From: Ralphred Date: Sun, 23 Feb 2025 21:01:35 +0000 (+0000) Subject: new file: sys-kernel/raspberrypi-sources/raspberrypi-sources-6.6.74_p20250127... X-Git-Url: https://git.liquid.me.uk/?a=commitdiff_plain;h=2b5fb0854200c27025f6563d732dde6fd99a4c8a;p=local new file: sys-kernel/raspberrypi-sources/raspberrypi-sources-6.6.74_p20250127.ebuild --- diff --git a/sys-kernel/raspberrypi-sources/Manifest b/sys-kernel/raspberrypi-sources/Manifest new file mode 100644 index 0000000..5d3395a --- /dev/null +++ b/sys-kernel/raspberrypi-sources/Manifest @@ -0,0 +1,7 @@ +AUX raspberrypi-sources-5.15.32-gentoo-kconfig.patch 14618 BLAKE2B d2b6bb66b8ddf7b17fff2b7e6d55cdd8360d6d4f0f033e513e34f5474c0078f21bd49ba6e8d28794ba1468e50f0e463fd79d64aa64f5d50025097e4fe733a2ac SHA512 6cd674d3dce0cd0565f28f903ff7d1b0beb492c4fa5c8f8c52c9b99c5da6f7d15e77e42d78b8671a73451033602815b3b858d59638c22a3c7c69d0e43d6ff4e1 +AUX raspberrypi-sources-6.1.21-gentoo-kconfig.patch 329 BLAKE2B f2c3f97b193968a2ad17547ffc7583af292c5bc17daa283c965ca5eb7115b94022a1d72000f1b9af03086aa7927b43c39ee10b12cfa143cb75ba423a7855bba4 SHA512 c00e9608b7bb03ee1637849e819d5a148ef4fb5a88490ac6567fe7720ef723666cc5e682cdf578ec2a7ea2a64f38828aef8847a2781d620571dfa5fa31ddf9d5 +AUX raspberrypi-sources-AI.patch 241035 BLAKE2B 4d34c2bc7139941e5e99948ece97b22614a3ead17cbc2b9e40aebbde15c796988f86e15959cc280fe2ae9bdac4edf42a67f21ea7e660d8c985702167d76f5120 SHA512 25587af6dae2a155c89a0deae2b9db9cf1b10cf7e2b6bb704edd9e55b5bce05e24aad7a7943c361861af0048fc6e82b7722424ac342572099300ee8402e847e8 +DIST genpatches-6.6-54.base.tar.xz 2922380 BLAKE2B f65404127bb6547208aeddac1996a4a2659bd99ff7429bcaff28247a867c000e962457725eab80db2c76297f1e5c27806dc6fb23e31d5694b6df783e65995227 SHA512 102f721f87478ad18599bb5ff65cd236180dd3d9d058786a5306cb36be3f30a2d2ef684e83b6f458d4de78a196323e87d346eba704bfbce733010aed6ac3d7f9 +DIST genpatches-6.6-54.extras.tar.xz 4060 BLAKE2B 22ea7d143bfe168bc5d9d30832423bcd33b49c1b3b5ddf031000d7d9fce96f6fa0fb1d06fd33b27a5de9c9f3833a139557f0ed7408ef12b23568784ff38ca7a9 SHA512 1b1a229b5923e7f3426139b17608b5feae6061313f7aea66de23f09a54696def39bf4b384c4e884a3e5c758c08e04dcfe1ab7f2aa331db74b59f5e511c6f2c5b +DIST linux-6.6.74_p20250127-raspberrypi.tar.gz 231349139 BLAKE2B ecd1a72b6a908f4903191cacf27f036038719e132e14eb7dc25a195b39056a3b3d6fbbd8477fbceea35437ef4853e71c13175fc1ce207c43af7e8a4ba3a55ef5 SHA512 f84ec3ea33dd95ac9d4c2a601a831e62e811687da283d2ee2bc6985b115d528837569a81176f05fe4a9d9e3b40e8e071aeb3f4088da583b1d2eafbb3929d6d98 +EBUILD raspberrypi-sources-6.6.74_p20250127.ebuild 1639 BLAKE2B 4aee43ed2c22ae4d2a470a601dd593210d806435e0dc75648214c5682fb3d99529f4bdef90fa644ecaf62f803d89478fdce8c257185ceb5e0b06b1ca4fc3bc8c SHA512 de99f89f34b70e0985188bf5f321aec8a09aaaff2618c9470b56ccd534c30e9c2c8a4e9321d182502cd7dddf20be4f714f19103e0198a656343ec703cb32bb11 diff --git a/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-5.15.32-gentoo-kconfig.patch b/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-5.15.32-gentoo-kconfig.patch new file mode 100644 index 0000000..8c8535a --- /dev/null +++ b/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-5.15.32-gentoo-kconfig.patch @@ -0,0 +1,474 @@ +diff --git a/Kconfig b/Kconfig +index 745bc773f567..16628b0c76d9 100644 +--- a/Kconfig ++++ b/Kconfig +@@ -5,6 +5,8 @@ + # + mainmenu "Linux/$(ARCH) $(KERNELVERSION) Kernel Configuration" + ++source "distro/Kconfig" ++ + source "scripts/Kconfig.include" + + source "init/Kconfig" +diff --git a/distro/Kconfig b/distro/Kconfig +new file mode 100644 +index 000000000000..531a636c959d +--- /dev/null ++++ b/distro/Kconfig +@@ -0,0 +1,378 @@ ++menu "Gentoo Linux" ++ ++config GENTOO_LINUX ++ bool "Gentoo Linux support" ++ ++ default y ++ ++ select CPU_FREQ_DEFAULT_GOV_SCHEDUTIL ++ ++ help ++ In order to boot Gentoo Linux a minimal set of config settings needs to ++ be enabled in the kernel; to avoid the users from having to enable them ++ manually as part of a Gentoo Linux installation or a new clean config, ++ we enable these config settings by default for convenience. ++ ++ See the settings that become available for more details and fine-tuning. ++ ++config GENTOO_LINUX_UDEV ++ bool "Linux dynamic and persistent device naming (userspace devfs) support" ++ ++ depends on GENTOO_LINUX ++ default y if GENTOO_LINUX ++ ++ select DEVTMPFS ++ select TMPFS ++ select UNIX ++ ++ select MMU ++ select SHMEM ++ ++ help ++ In order to boot Gentoo Linux a minimal set of config settings needs to ++ be enabled in the kernel; to avoid the users from having to enable them ++ manually as part of a Gentoo Linux installation or a new clean config, ++ we enable these config settings by default for convenience. ++ ++ Currently this only selects TMPFS, DEVTMPFS and their dependencies. ++ TMPFS is enabled to maintain a tmpfs file system at /dev/shm, /run and ++ /sys/fs/cgroup; DEVTMPFS to maintain a devtmpfs file system at /dev. ++ ++ Some of these are critical files that need to be available early in the ++ boot process; if not available, it causes sysfs and udev to malfunction. ++ ++ To ensure Gentoo Linux boots, it is best to leave this setting enabled; ++ if you run a custom setup, you could consider whether to disable this. ++ ++config GENTOO_LINUX_PORTAGE ++ bool "Select options required by Portage features" ++ ++ depends on GENTOO_LINUX ++ default y if GENTOO_LINUX ++ ++ select CGROUPS ++ select NAMESPACES ++ select IPC_NS ++ select NET_NS ++ select PID_NS ++ select SYSVIPC ++ select USER_NS ++ select UTS_NS ++ ++ help ++ This enables options required by various Portage FEATURES. ++ Currently this selects: ++ ++ CGROUPS (required for FEATURES=cgroup) ++ IPC_NS (required for FEATURES=ipc-sandbox) ++ NET_NS (required for FEATURES=network-sandbox) ++ PID_NS (required for FEATURES=pid-sandbox) ++ SYSVIPC (required by IPC_NS) ++ ++ ++ It is highly recommended that you leave this enabled as these FEATURES ++ are, or will soon be, enabled by default. ++ ++menu "Support for init systems, system and service managers" ++ visible if GENTOO_LINUX ++ ++config GENTOO_LINUX_INIT_SCRIPT ++ bool "OpenRC, runit and other script based systems and managers" ++ ++ default n ++ ++ depends on GENTOO_LINUX ++ ++ select BINFMT_SCRIPT ++ select CGROUPS ++ select EPOLL ++ select FILE_LOCKING ++ select INOTIFY_USER ++ select SIGNALFD ++ select TIMERFD ++ ++ help ++ The init system is the first thing that loads after the kernel booted. ++ ++ These config settings allow you to select which init systems to support; ++ instead of having to select all the individual settings all over the ++ place, these settings allows you to select all the settings at once. ++ ++ This particular setting enables all the known requirements for OpenRC, ++ runit and similar script based systems and managers. ++ ++ If you are unsure about this, it is best to leave this setting enabled. ++ ++menuconfig GENTOO_LINUX_INIT_SYSTEMD ++ bool "systemd" ++ default y ++ depends on GENTOO_LINUX && GENTOO_LINUX_UDEV ++ ++ select CGROUPS ++ select DEVTMPFS ++ select DMIID if X86_32 || X86_64 || X86 ++ select EPOLL ++ select FHANDLE ++ select INOTIFY_USER ++ select PROC_FS ++ select SIGNALFD ++ select SYSFS ++ select TIMERFD ++ select UNIX ++ ++ help ++ The init system is the first thing that loads after the kernel booted. ++ ++ These config settings allow you to select which init systems to support; ++ instead of having to select all the individual settings all over the ++ place, these settings allows you to select all the settings at once. ++ ++ This particular setting enables all the known requirements for systemd; ++ it also enables suggested optional settings, as the package suggests to. ++ ++if GENTOO_LINUX_INIT_SYSTEMD ++ ++comment "systemd config options" ++ ++config GENTOO_LINUX_INIT_SYSTEMD_CRYPTO ++ bool "Systemd crypto/hash api" ++ default y ++ select CRYPTO_HMAC ++ select CRYPTO_SHA256 ++ select CRYPTO_USER_API_HASH ++ ++config GENTOO_LINUX_INIT_SYSTEMD_SCSI ++ bool "Support for some SCSI devices serial number retrieval" ++ default n ++ select BLK_DEV_BSG ++ ++config GENTOO_LINUX_INIT_SYSTEMD_PRIVATE_NETWORKS ++ bool "Support PrivateNetwork directive in service units" ++ default y ++ select NET_NS ++ ++ help ++ systemd-localed.service and other systemd units use ++ PrivateNetwork so this is effectively required ++ ++config GENTOO_LINUX_INIT_SYSTEMD_PRIVATE_USERS ++ bool "Support PrivateUsers directive in service units" ++ default y ++ select USER_NS ++ ++config GENTOO_LINUX_INIT_SYSTEMD_CPUSHARES ++ bool "Support CPUShares in resource control unit settings" ++ default y ++ select CGROUP_SCHED ++ select FAIR_GROUP_SCHED ++ ++config GENTOO_LINUX_INIT_SYSTEMD_CPUQUOTA ++ bool "Support CPUQuota in resource control unit settings" ++ default y ++ select CFS_BANDWIDTH ++ ++config GENTOO_LINUX_INIT_SYSTEMD_NETWORK_FILTERING ++ bool "Support network traffic filtering for IP packets and custom network traffic filters in resource control unit settings" ++ default y ++ select BPF ++ select BPF_SYSCALL ++ select BPF_JIT ++ select CGROUP_BPF ++ select HAVE_EBPF_JIT ++ ++ help ++ Required for IPAddressDeny=, IPAddressAllow=, IPIngressFilterPath=, ++ IPEgressFilterPath= in resource control unit settings ++ ++config GENTOO_LINUX_INIT_SYSTEMD_NETWORK ++ bool "Restrict Sockets and Network Interface access in resource control unit settings" ++ default y ++ select BPF ++ select BPF_SYSCALL ++ select BPF_JIT ++ select CGROUP_BPF ++ select EFIVAR_FS if ( X86_32 || X86_64 || X86 ) && CONFIG_EFI ++ select EFI_PARTITION if ( X86_32 || X86_64 || X86 ) && CONFIG_EFI ++ select HAVE_EBPF_JIT ++ ++ help ++ Allow or deny binding a socket address to a socket by matching ++ it with the bind-rule and applying a corresponding action if ++ there is a match. Also restricts the network interfaces that ++ processes of this unit can use. ++ ++config GENTOO_LINUX_INIT_SYSTEMD_FILESYSTEMS ++ bool "Support RestrictFileSystems directive in service units" ++ default n ++ depends on !GENTOO_KERNEL_SELF_PROTECTION ++ select BPF ++ select BPF_SYSCALL ++ select BPF_LSM ++ select DEBUG_INFO_BTF ++ ++ help ++ Also required to select LSM="...,bpf" or kernel booted with ++ lsm="...,bpf". ++ ++config GENTOO_LINUX_INIT_SYSTEMD_OOMD ++ bool "Support systemd-oomd" ++ default y ++ select PSI ++ ++config GENTOO_LINUX_INIT_SYSTEMD_VERITY ++ bool "Support signed Verity images" ++ default n ++ select DM_VERITY_VERIFY_ROOTHASH_SIG ++ ++config GENTOO_LINUX_INIT_SYSTEMD_OPTIONAL ++ bool "Optional but strongly recommended modules" ++ default y ++ select AUTOFS_FS ++ select IPV6 ++ select KCMP ++ select SECCOMP ++ select SECCOMP_FILTER if HAVE_ARCH_SECCOMP_FILTER ++ select TMPFS_XATTR ++ ++ select 9P_FS_POSIX_ACL if 9P_FS ++ select BTRFS_FS_POSIX_ACL if BTRFS_FS ++ select CEPH_FS_POSIX_ACL if CEPH_FS ++ select EROFS_FS_POSIX_ACL if EROFS_FS ++ select EXT2_FS_POSIX_ACL if EXT2_FS3 ++ select EXT3_FS_POSIX_ACL if EXT3_FS ++ select EXT4_FS_POSIX_ACL if EXT4_FS ++ select F2FS_FS_POSIX_ACL if F2FS_FS ++ select NTFS3_FS_POSIX_ACL if NTFS3_FS ++ select REISERFS_FS_POSIX_ACL if REISERFS_FS ++ select TMPFS_POSIX_ACL if TMPFS ++ select XFS_POSIX_ACL if XFS_FS ++endif ++endmenu ++ ++menuconfig GENTOO_KERNEL_SELF_PROTECTION ++ bool "Kernel Self Protection Project" ++ depends on GENTOO_LINUX ++ help ++ Recommended Kernel settings based on the suggestions from the Kernel Self Protection Project ++ See: https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Recommended_Settings ++ Note, there may be additional settings for which the CONFIG_ setting is invisible in menuconfig due ++ to unmet dependencies. Search for GENTOO_KERNEL_SELF_PROTECTION_COMMON and search for ++ GENTOO_KERNEL_SELF_PROTECTION_{X86_64, ARM64, X86_32, ARM} for dependency information on your ++ specific architecture. ++ Note 2: Please see the URL above for numeric settings, e.g. CONFIG_DEFAULT_MMAP_MIN_ADDR=65536 ++ for X86_64 ++ ++if GENTOO_KERNEL_SELF_PROTECTION ++config GENTOO_KERNEL_SELF_PROTECTION_COMMON ++ bool "Enable Kernel Self Protection Project Recommendations" ++ ++ depends on GENTOO_LINUX && !ACPI_CUSTOM_METHOD && !COMPAT_BRK && !DEVKMEM && !PROC_KCORE && !COMPAT_VDSO && !KEXEC && !HIBERNATION && !LEGACY_PTYS && !X86_X32 && !MODIFY_LDT_SYSCALL && GCC_PLUGINS && !GENTOO_LINUX_INIT_SYSTEMD_FILESYSTEMS ++ ++ select BUG ++ select STRICT_KERNEL_RWX ++ select DEBUG_WX ++ select STACKPROTECTOR ++ select STACKPROTECTOR_STRONG ++ select STRICT_DEVMEM if DEVMEM=y ++ select IO_STRICT_DEVMEM if DEVMEM=y ++ select SYN_COOKIES ++ select DEBUG_CREDENTIALS ++ select DEBUG_NOTIFIERS ++ select DEBUG_LIST ++ select DEBUG_SG ++ select BUG_ON_DATA_CORRUPTION ++ select SCHED_STACK_END_CHECK ++ select SECCOMP if HAVE_ARCH_SECCOMP ++ select SECCOMP_FILTER if HAVE_ARCH_SECCOMP_FILTER ++ select SECURITY_YAMA ++ select SLAB_FREELIST_RANDOM ++ select SLAB_FREELIST_HARDENED ++ select SHUFFLE_PAGE_ALLOCATOR ++ select SLUB_DEBUG ++ select PAGE_POISONING ++ select PAGE_POISONING_NO_SANITY ++ select PAGE_POISONING_ZERO ++ select INIT_ON_ALLOC_DEFAULT_ON ++ select INIT_ON_FREE_DEFAULT_ON ++ select REFCOUNT_FULL ++ select FORTIFY_SOURCE ++ select SECURITY_DMESG_RESTRICT ++ select PANIC_ON_OOPS ++ select GCC_PLUGIN_LATENT_ENTROPY ++ select GCC_PLUGIN_STRUCTLEAK ++ select GCC_PLUGIN_STRUCTLEAK_BYREF_ALL ++ select GCC_PLUGIN_RANDSTRUCT ++ select GCC_PLUGIN_RANDSTRUCT_PERFORMANCE ++ ++ help ++ Search for GENTOO_KERNEL_SELF_PROTECTION_{X86_64, ARM64, X86_32, ARM} for dependency ++ information on your specific architecture. Note 2: Please see the URL above for ++ numeric settings, e.g. CONFIG_DEFAULT_MMAP_MIN_ADDR=65536 for X86_64 ++ ++config GENTOO_KERNEL_SELF_PROTECTION_X86_64 ++ bool "X86_64 KSPP Settings" if GENTOO_KERNEL_SELF_PROTECTION_COMMON ++ ++ depends on !X86_MSR && X86_64 && GENTOO_KERNEL_SELF_PROTECTION ++ default n ++ ++ select RANDOMIZE_BASE ++ select RANDOMIZE_MEMORY ++ select RELOCATABLE ++ select LEGACY_VSYSCALL_NONE ++ select PAGE_TABLE_ISOLATION ++ select GCC_PLUGIN_STACKLEAK ++ select VMAP_STACK ++ ++ ++config GENTOO_KERNEL_SELF_PROTECTION_ARM64 ++ bool "ARM64 KSPP Settings" ++ ++ depends on ARM64 ++ default n ++ ++ select RANDOMIZE_BASE ++ select RELOCATABLE ++ select ARM64_SW_TTBR0_PAN ++ select CONFIG_UNMAP_KERNEL_AT_EL0 ++ select GCC_PLUGIN_STACKLEAK ++ select VMAP_STACK ++ ++config GENTOO_KERNEL_SELF_PROTECTION_X86_32 ++ bool "X86_32 KSPP Settings" ++ ++ depends on !X86_MSR && !MODIFY_LDT_SYSCALL && !M486 && X86_32 ++ default n ++ ++ select HIGHMEM64G ++ select X86_PAE ++ select RANDOMIZE_BASE ++ select RELOCATABLE ++ select PAGE_TABLE_ISOLATION ++ ++config GENTOO_KERNEL_SELF_PROTECTION_ARM ++ bool "ARM KSPP Settings" ++ ++ depends on !OABI_COMPAT && ARM ++ default n ++ ++ select VMSPLIT_3G ++ select STRICT_MEMORY_RWX ++ select CPU_SW_DOMAIN_PAN ++ ++endif ++ ++config GENTOO_PRINT_FIRMWARE_INFO ++ bool "Print firmware information that the kernel attempts to load" ++ ++ depends on GENTOO_LINUX ++ default y ++ ++ help ++ Enable this option to print information about firmware that the kernel ++ is attempting to load. This information can be accessible via the ++ dmesg command-line utility ++ ++ See the settings that become available for more details and fine-tuning. ++ ++endmenu ++ +diff --git a/drivers/base/firmware_loader/Kconfig b/drivers/base/firmware_loader/Kconfig +index 5b24f3959255..3e470a3dab39 100644 +--- a/drivers/base/firmware_loader/Kconfig ++++ b/drivers/base/firmware_loader/Kconfig +@@ -70,6 +70,7 @@ config EXTRA_FIRMWARE_DIR + + config FW_LOADER_USER_HELPER + bool "Enable the firmware sysfs fallback mechanism" ++ depends on !GENTOO_LINUX_INIT_SYSTEMD + select FW_LOADER_PAGED_BUF + help + This option enables a sysfs loading facility to enable firmware +diff --git a/init/Kconfig b/init/Kconfig +index 11f8a845f259..c826ddeb9ab3 100644 +--- a/init/Kconfig ++++ b/init/Kconfig +@@ -1008,6 +1008,7 @@ config CFS_BANDWIDTH + config RT_GROUP_SCHED + bool "Group scheduling for SCHED_RR/FIFO" + depends on CGROUP_SCHED ++ depends on !GENTOO_LINUX_INIT_SYSTEMD + default n + help + This feature lets you explicitly allocate real CPU bandwidth +@@ -1272,6 +1273,7 @@ config SCHED_AUTOGROUP + config SYSFS_DEPRECATED + bool "Enable deprecated sysfs features to support old userspace tools" + depends on SYSFS ++ depends on !GENTOO_LINUX_INIT_SYSTEMD + default n + help + This option adds code that switches the layout of the "block" class +diff --git a/mm/Kconfig b/mm/Kconfig +index c048dea7e342..81a1dfd69adc 100644 +--- a/mm/Kconfig ++++ b/mm/Kconfig +@@ -305,6 +305,8 @@ config KSM + config DEFAULT_MMAP_MIN_ADDR + int "Low address space to protect from user allocation" + depends on MMU ++ default 65536 if ( X86_64 || X86_32 || PPC64 || IA64 ) && GENTOO_KERNEL_SELF_PROTECTION ++ default 32768 if ( ARM64 || ARM ) && GENTOO_KERNEL_SELF_PROTECTION + default 4096 + help + This is the portion of low virtual memory which should be protected +diff --git a/security/Kconfig b/security/Kconfig +index fe6c0395fa02..6b1b81cd120e 100644 +--- a/security/Kconfig ++++ b/security/Kconfig +@@ -166,6 +166,7 @@ config HARDENED_USERCOPY + config HARDENED_USERCOPY_FALLBACK + bool "Allow usercopy whitelist violations to fallback to object size" + depends on HARDENED_USERCOPY ++ depends on !GENTOO_KERNEL_SELF_PROTECTION + default y + help + This is a temporary option that allows missing usercopy whitelists +@@ -181,6 +182,7 @@ config HARDENED_USERCOPY_PAGESPAN + bool "Refuse to copy allocations that span multiple pages" + depends on HARDENED_USERCOPY + depends on EXPERT ++ depends on !GENTOO_KERNEL_SELF_PROTECTION + help + When a multi-page allocation is done without __GFP_COMP, + hardened usercopy will reject attempts to copy it. There are, +diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig +index 9e921fc72538..9e1f7ce887b6 100644 +--- a/security/selinux/Kconfig ++++ b/security/selinux/Kconfig +@@ -12,6 +12,7 @@ config SECURITY_SELINUX + config SECURITY_SELINUX_BOOTPARAM + bool "NSA SELinux boot parameter" + depends on SECURITY_SELINUX ++ depends on !GENTOO_KERNEL_SELF_PROTECTION + default n + help + This option adds a kernel parameter 'selinux', which allows SELinux diff --git a/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-6.1.21-gentoo-kconfig.patch b/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-6.1.21-gentoo-kconfig.patch new file mode 100644 index 0000000..941501e --- /dev/null +++ b/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-6.1.21-gentoo-kconfig.patch @@ -0,0 +1,13 @@ +--- a/drivers/base/firmware_loader/Kconfig ++++ b/drivers/base/firmware_loader/Kconfig +@@ -75,6 +75,7 @@ config EXTRA_FIRMWARE_DIR + + config FW_LOADER_USER_HELPER + bool "Enable the firmware sysfs fallback mechanism" ++ depends on !GENTOO_LINUX_INIT_SYSTEMD + select FW_LOADER_SYSFS + select FW_LOADER_PAGED_BUF + help +-- +2.41.0 + diff --git a/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-AI.patch b/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-AI.patch new file mode 100644 index 0000000..a2a640b --- /dev/null +++ b/sys-kernel/raspberrypi-sources/files/raspberrypi-sources-AI.patch @@ -0,0 +1,7864 @@ +diff '--color=auto' -urN a/drivers/staging/gasket-driver/apex_driver.c b/drivers/staging/gasket-driver/apex_driver.c +--- a/drivers/staging/gasket-driver/apex_driver.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/apex_driver.c 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,1243 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Driver for the Apex chip. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "apex.h" ++ ++#include "gasket_core.h" ++#include "gasket_interrupt.h" ++#include "gasket_page_table.h" ++#include "gasket_sysfs.h" ++ ++/* Constants */ ++#define APEX_DEVICE_NAME "Apex" ++#define APEX_DRIVER_VERSION "1.2" ++ ++/* CSRs are in BAR 2. */ ++#define APEX_BAR_INDEX 2 ++ ++#define APEX_PCI_VENDOR_ID 0x1ac1 ++#define APEX_PCI_DEVICE_ID 0x089a ++ ++/* Bar Offsets. */ ++#define APEX_BAR_OFFSET 0 ++#define APEX_CM_OFFSET 0x1000000 ++ ++/* The sizes of each Apex BAR 2. */ ++#define APEX_BAR_BYTES 0x100000 ++#define APEX_CH_MEM_BYTES (PAGE_SIZE * MAX_NUM_COHERENT_PAGES) ++ ++/* The number of user-mappable memory ranges in BAR2 of a Apex chip. */ ++#define NUM_REGIONS 3 ++ ++/* The number of nodes in a Apex chip. */ ++#define NUM_NODES 1 ++ ++/* ++ * The total number of entries in the page table. Should match the value read ++ * from the register APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_SIZE. ++ */ ++#define APEX_PAGE_TABLE_TOTAL_ENTRIES 8192 ++ ++#define APEX_EXTENDED_SHIFT 63 /* Extended address bit position. */ ++ ++/* Check reset 120 times */ ++#define APEX_RESET_RETRY 120 ++/* Wait 100 ms between checks. Total 12 sec wait maximum. */ ++#define APEX_RESET_DELAY 100 ++ ++/* Interval between temperature polls, 0 disables polling */ ++#define DEFAULT_APEX_TEMP_POLL_INTERVAL 5000 ++ ++/* apex device private data */ ++struct apex_dev { ++ struct gasket_dev *gasket_dev_ptr; ++ struct delayed_work check_temperature_work; ++ u32 adc_trip_points[3]; ++ atomic_t temp_poll_interval; ++ u32 hw_temp_warn1_adc; ++ u32 hw_temp_warn2_adc; ++ bool hw_temp_warn1_en; ++ bool hw_temp_warn2_en; ++}; ++ ++/* Enumeration of the supported sysfs entries. */ ++enum sysfs_attribute_type { ++ ATTR_KERNEL_HIB_PAGE_TABLE_SIZE, ++ ATTR_KERNEL_HIB_SIMPLE_PAGE_TABLE_SIZE, ++ ATTR_KERNEL_HIB_NUM_ACTIVE_PAGES, ++ ATTR_TEMP, ++ ATTR_TEMP_WARN1, ++ ATTR_TEMP_WARN1_EN, ++ ATTR_TEMP_WARN2, ++ ATTR_TEMP_WARN2_EN, ++ ATTR_TEMP_TRIP0, ++ ATTR_TEMP_TRIP1, ++ ATTR_TEMP_TRIP2, ++ ATTR_TEMP_POLL_INTERVAL, ++ ATTR_UNIQUE_ID, ++}; ++ ++/* ++ * Register offsets into BAR2 memory. ++ * Only values necessary for driver implementation are defined. ++ */ ++enum apex_bar2_regs { ++ APEX_BAR2_REG_SCU_BASE = 0x1A300, ++ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_SIZE = 0x46000, ++ APEX_BAR2_REG_KERNEL_HIB_EXTENDED_TABLE = 0x46008, ++ APEX_BAR2_REG_KERNEL_HIB_TRANSLATION_ENABLE = 0x46010, ++ APEX_BAR2_REG_KERNEL_HIB_INSTR_QUEUE_INTVECCTL = 0x46018, ++ APEX_BAR2_REG_KERNEL_HIB_INPUT_ACTV_QUEUE_INTVECCTL = 0x46020, ++ APEX_BAR2_REG_KERNEL_HIB_PARAM_QUEUE_INTVECCTL = 0x46028, ++ APEX_BAR2_REG_KERNEL_HIB_OUTPUT_ACTV_QUEUE_INTVECCTL = 0x46030, ++ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL = 0x46038, ++ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL = 0x46040, ++ APEX_BAR2_REG_KERNEL_HIB_FATAL_ERR_INTVECCTL = 0x46048, ++ APEX_BAR2_REG_KERNEL_HIB_DMA_PAUSE = 0x46050, ++ APEX_BAR2_REG_KERNEL_HIB_DMA_PAUSE_MASK = 0x46058, ++ APEX_BAR2_REG_KERNEL_HIB_STATUS_BLOCK_DELAY = 0x46060, ++ APEX_BAR2_REG_KERNEL_HIB_MSIX_PENDING_BIT_ARRAY0 = 0x46068, ++ APEX_BAR2_REG_KERNEL_HIB_MSIX_PENDING_BIT_ARRAY1 = 0x46070, ++ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_INIT = 0x46078, ++ APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE_INIT = 0x46080, ++ APEX_BAR2_REG_KERNEL_WIRE_INT_PENDING_BIT_ARRAY = 0x48778, ++ APEX_BAR2_REG_KERNEL_WIRE_INT_MASK_ARRAY = 0x48780, ++ APEX_BAR2_REG_USER_HIB_DMA_PAUSE = 0x486D8, ++ APEX_BAR2_REG_USER_HIB_DMA_PAUSED = 0x486E0, ++ APEX_BAR2_REG_IDLEGENERATOR_IDLEGEN_IDLEREGISTER = 0x4A000, ++ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE = 0x50000, ++ APEX_BAR2_REG_OMC0_D0 = 0x01a0d0, ++ APEX_BAR2_REG_OMC0_D4 = 0x01a0d4, ++ APEX_BAR2_REG_OMC0_D8 = 0x01a0d8, ++ APEX_BAR2_REG_OMC0_DC = 0x01a0dc, ++ APEX_BAR2_REG_EFUSE_DC = 0x01a2dc, ++ APEX_BAR2_REG_EFUSE_E0 = 0x01a2e0, ++ APEX_BAR2_REG_EFUSE_E4 = 0x01a2e4, ++ APEX_BAR2_REG_EFUSE_E8 = 0x01a2e8, ++ ++ /* Error registers - Used mostly for debug */ ++ APEX_BAR2_REG_USER_HIB_ERROR_STATUS = 0x86f0, ++ APEX_BAR2_REG_SCALAR_CORE_ERROR_STATUS = 0x41a0, ++}; ++ ++/* Addresses for packed registers. */ ++#define APEX_BAR2_REG_AXI_QUIESCE (APEX_BAR2_REG_SCU_BASE + 0x2C) ++#define APEX_BAR2_REG_GCB_CLOCK_GATE (APEX_BAR2_REG_SCU_BASE + 0x14) ++#define APEX_BAR2_REG_SCU_0 (APEX_BAR2_REG_SCU_BASE + 0xc) ++#define APEX_BAR2_REG_SCU_1 (APEX_BAR2_REG_SCU_BASE + 0x10) ++#define APEX_BAR2_REG_SCU_2 (APEX_BAR2_REG_SCU_BASE + 0x14) ++#define APEX_BAR2_REG_SCU_3 (APEX_BAR2_REG_SCU_BASE + 0x18) ++#define APEX_BAR2_REG_SCU_4 (APEX_BAR2_REG_SCU_BASE + 0x1c) ++#define APEX_BAR2_REG_SCU_5 (APEX_BAR2_REG_SCU_BASE + 0x20) ++ ++#define SCU3_RG_PWR_STATE_OVR_BIT_OFFSET 26 ++#define SCU3_RG_PWR_STATE_OVR_MASK_WIDTH 2 ++#define SCU3_CUR_RST_GCB_BIT_MASK 0x10 ++#define SCU2_RG_RST_GCB_BIT_MASK 0xc ++ ++/* Configuration for page table. */ ++static struct gasket_page_table_config apex_page_table_configs[NUM_NODES] = { ++ { ++ .id = 0, ++ .mode = GASKET_PAGE_TABLE_MODE_NORMAL, ++ .total_entries = APEX_PAGE_TABLE_TOTAL_ENTRIES, ++ .base_reg = APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE, ++ .extended_reg = APEX_BAR2_REG_KERNEL_HIB_EXTENDED_TABLE, ++ .extended_bit = APEX_EXTENDED_SHIFT, ++ }, ++}; ++ ++/* The regions in the BAR2 space that can be mapped into user space. */ ++static const struct gasket_mappable_region mappable_regions[NUM_REGIONS] = { ++ { 0x40000, 0x1000 }, ++ { 0x44000, 0x1000 }, ++ { 0x48000, 0x1000 }, ++}; ++ ++/* Gasket device interrupts enums must be dense (i.e., no empty slots). */ ++enum apex_interrupt { ++ APEX_INTERRUPT_INSTR_QUEUE = 0, ++ APEX_INTERRUPT_INPUT_ACTV_QUEUE = 1, ++ APEX_INTERRUPT_PARAM_QUEUE = 2, ++ APEX_INTERRUPT_OUTPUT_ACTV_QUEUE = 3, ++ APEX_INTERRUPT_SC_HOST_0 = 4, ++ APEX_INTERRUPT_SC_HOST_1 = 5, ++ APEX_INTERRUPT_SC_HOST_2 = 6, ++ APEX_INTERRUPT_SC_HOST_3 = 7, ++ APEX_INTERRUPT_TOP_LEVEL_0 = 8, ++ APEX_INTERRUPT_TOP_LEVEL_1 = 9, ++ APEX_INTERRUPT_TOP_LEVEL_2 = 10, ++ APEX_INTERRUPT_TOP_LEVEL_3 = 11, ++ APEX_INTERRUPT_FATAL_ERR = 12, ++ APEX_INTERRUPT_COUNT = 13, ++}; ++ ++/* Interrupt descriptors for Apex */ ++static struct gasket_interrupt_desc apex_interrupts[] = { ++ { ++ APEX_INTERRUPT_INSTR_QUEUE, ++ APEX_BAR2_REG_KERNEL_HIB_INSTR_QUEUE_INTVECCTL, ++ UNPACKED, ++ }, ++ { ++ APEX_INTERRUPT_INPUT_ACTV_QUEUE, ++ APEX_BAR2_REG_KERNEL_HIB_INPUT_ACTV_QUEUE_INTVECCTL, ++ UNPACKED ++ }, ++ { ++ APEX_INTERRUPT_PARAM_QUEUE, ++ APEX_BAR2_REG_KERNEL_HIB_PARAM_QUEUE_INTVECCTL, ++ UNPACKED ++ }, ++ { ++ APEX_INTERRUPT_OUTPUT_ACTV_QUEUE, ++ APEX_BAR2_REG_KERNEL_HIB_OUTPUT_ACTV_QUEUE_INTVECCTL, ++ UNPACKED ++ }, ++ { ++ APEX_INTERRUPT_SC_HOST_0, ++ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL, ++ PACK_0 ++ }, ++ { ++ APEX_INTERRUPT_SC_HOST_1, ++ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL, ++ PACK_1 ++ }, ++ { ++ APEX_INTERRUPT_SC_HOST_2, ++ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL, ++ PACK_2 ++ }, ++ { ++ APEX_INTERRUPT_SC_HOST_3, ++ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL, ++ PACK_3 ++ }, ++ { ++ APEX_INTERRUPT_TOP_LEVEL_0, ++ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL, ++ PACK_0 ++ }, ++ { ++ APEX_INTERRUPT_TOP_LEVEL_1, ++ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL, ++ PACK_1 ++ }, ++ { ++ APEX_INTERRUPT_TOP_LEVEL_2, ++ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL, ++ PACK_2 ++ }, ++ { ++ APEX_INTERRUPT_TOP_LEVEL_3, ++ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL, ++ PACK_3 ++ }, ++ { ++ APEX_INTERRUPT_FATAL_ERR, ++ APEX_BAR2_REG_KERNEL_HIB_FATAL_ERR_INTVECCTL, ++ UNPACKED ++ }, ++}; ++ ++/* Allows device to enter power save upon driver close(). */ ++static int allow_power_save = 1; ++ ++/* Allows SW based clock gating. */ ++static int allow_sw_clock_gating; ++ ++/* Allows HW based clock gating. */ ++/* Note: this is not mutual exclusive with SW clock gating. */ ++static int allow_hw_clock_gating = 1; ++ ++/* Act as if only GCB is instantiated. */ ++static int bypass_top_level; ++ ++module_param(allow_power_save, int, 0644); ++module_param(allow_sw_clock_gating, int, 0644); ++module_param(allow_hw_clock_gating, int, 0644); ++module_param(bypass_top_level, int, 0644); ++ ++/* Temperature points in milli C at which DFS is toggled */ ++#define DEFAULT_TRIP_POINT0_TEMP 85000 ++#define DEFAULT_TRIP_POINT1_TEMP 90000 ++#define DEFAULT_TRIP_POINT2_TEMP 95000 ++ ++static int trip_point0_temp = DEFAULT_TRIP_POINT0_TEMP; ++static int trip_point1_temp = DEFAULT_TRIP_POINT1_TEMP; ++static int trip_point2_temp = DEFAULT_TRIP_POINT2_TEMP; ++ ++module_param(trip_point0_temp, int, 0644); ++module_param(trip_point1_temp, int, 0644); ++module_param(trip_point2_temp, int, 0644); ++ ++/* Hardware monitored temperature trip points in milli C ++ Apex chip drives INTR line when reaching hw_temp_warn1 temperature, ++ and SD_ALARM line when reaching hw_temp_warn2 if corresponding ++ hw_temp_warn*_en is set to true. ++ */ ++static int hw_temp_warn1 = 100000; ++static int hw_temp_warn2 = 100000; ++static bool hw_temp_warn1_en = false; ++static bool hw_temp_warn2_en = true; ++ ++module_param(hw_temp_warn1, int, 0644); ++module_param(hw_temp_warn2, int, 0644); ++module_param(hw_temp_warn1_en, bool, 0644); ++module_param(hw_temp_warn2_en, bool, 0644); ++ ++/* Temperature poll interval in ms */ ++static int temp_poll_interval = DEFAULT_APEX_TEMP_POLL_INTERVAL; ++module_param(temp_poll_interval, int, 0644); ++ ++/* Check the device status registers and return device status ALIVE or DEAD. */ ++static int apex_get_status(struct gasket_dev *gasket_dev) ++{ ++ /* TODO: Check device status. */ ++ return GASKET_STATUS_ALIVE; ++} ++ ++/* Enter GCB reset state. */ ++static int apex_enter_reset(struct gasket_dev *gasket_dev) ++{ ++ if (bypass_top_level) ++ return 0; ++ ++ /* ++ * Software reset: ++ * Enable sleep mode ++ * - Software force GCB idle ++ * - Enable GCB idle ++ */ ++ gasket_read_modify_write_64(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_IDLEGENERATOR_IDLEGEN_IDLEREGISTER, ++ 0x0, 1, 32); ++ ++ /* - Initiate DMA pause */ ++ gasket_dev_write_64(gasket_dev, 1, APEX_BAR_INDEX, ++ APEX_BAR2_REG_USER_HIB_DMA_PAUSE); ++ ++ /* - Wait for DMA pause complete. */ ++ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_USER_HIB_DMA_PAUSED, 1, 1, ++ APEX_RESET_DELAY, APEX_RESET_RETRY)) { ++ dev_err(gasket_dev->dev, ++ "DMAs did not quiesce within timeout (%d ms)\n", ++ APEX_RESET_RETRY * APEX_RESET_DELAY); ++ return -ETIMEDOUT; ++ } ++ ++ /* - Enable GCB reset (0x1 to rg_rst_gcb) */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_2, 0x1, 2, 2); ++ ++ /* - Enable GCB clock Gate (0x1 to rg_gated_gcb) */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_2, 0x1, 2, 18); ++ ++ /* - Enable GCB memory shut down (0x3 to rg_force_ram_sd) */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, 0x3, 2, 14); ++ ++ /* - Wait for RAM shutdown. */ ++ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, 1 << 6, 1 << 6, ++ APEX_RESET_DELAY, APEX_RESET_RETRY)) { ++ dev_err(gasket_dev->dev, ++ "RAM did not shut down within timeout (%d ms)\n", ++ APEX_RESET_RETRY * APEX_RESET_DELAY); ++ return -ETIMEDOUT; ++ } ++ ++ return 0; ++} ++ ++/* Quit GCB reset state. */ ++static int apex_quit_reset(struct gasket_dev *gasket_dev) ++{ ++ u32 val0, val1; ++ ++ if (bypass_top_level) ++ return 0; ++ ++ /* ++ * Disable sleep mode: ++ * - Disable GCB memory shut down: ++ * - b00: Not forced (HW controlled) ++ * - b1x: Force disable ++ */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, 0x0, 2, 14); ++ ++ /* ++ * - Disable software clock gate: ++ * - b00: Not forced (HW controlled) ++ * - b1x: Force disable ++ */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_2, 0x0, 2, 18); ++ ++ /* ++ * - Disable GCB reset (rg_rst_gcb): ++ * - b00: Not forced (HW controlled) ++ * - b1x: Force disable = Force not Reset ++ */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_2, 0x2, 2, 2); ++ ++ /* - Wait for RAM enable. */ ++ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, 1 << 6, 0, ++ APEX_RESET_DELAY, APEX_RESET_RETRY)) { ++ dev_err(gasket_dev->dev, ++ "RAM did not enable within timeout (%d ms)\n", ++ APEX_RESET_RETRY * APEX_RESET_DELAY); ++ return -ETIMEDOUT; ++ } ++ ++ /* - Wait for Reset complete. */ ++ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, ++ SCU3_CUR_RST_GCB_BIT_MASK, 0, ++ APEX_RESET_DELAY, APEX_RESET_RETRY)) { ++ dev_err(gasket_dev->dev, ++ "GCB did not leave reset within timeout (%d ms)\n", ++ APEX_RESET_RETRY * APEX_RESET_DELAY); ++ return -ETIMEDOUT; ++ } ++ ++ if (!allow_hw_clock_gating) { ++ val0 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ /* Inactive and Sleep mode are disabled. */ ++ gasket_read_modify_write_32(gasket_dev, ++ APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, 0x3, ++ SCU3_RG_PWR_STATE_OVR_MASK_WIDTH, ++ SCU3_RG_PWR_STATE_OVR_BIT_OFFSET); ++ val1 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ dev_dbg(gasket_dev->dev, ++ "Disallow HW clock gating 0x%x -> 0x%x\n", val0, val1); ++ } else { ++ val0 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ /* Inactive mode enabled - Sleep mode disabled. */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3, 2, ++ SCU3_RG_PWR_STATE_OVR_MASK_WIDTH, ++ SCU3_RG_PWR_STATE_OVR_BIT_OFFSET); ++ val1 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ dev_dbg(gasket_dev->dev, "Allow HW clock gating 0x%x -> 0x%x\n", ++ val0, val1); ++ } ++ ++ return 0; ++} ++ ++/* Reset the Apex hardware. Called on final close via device_close_cb. */ ++static int apex_device_cleanup(struct gasket_dev *gasket_dev) ++{ ++ u64 scalar_error; ++ u64 hib_error; ++ int ret = 0; ++ ++ hib_error = gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_USER_HIB_ERROR_STATUS); ++ scalar_error = gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCALAR_CORE_ERROR_STATUS); ++ ++ dev_dbg(gasket_dev->dev, ++ "%s 0x%p hib_error 0x%llx scalar_error 0x%llx\n", ++ __func__, gasket_dev, hib_error, scalar_error); ++ ++ if (allow_power_save) ++ ret = apex_enter_reset(gasket_dev); ++ ++ return ret; ++} ++ ++/* Determine if GCB is in reset state. */ ++static bool is_gcb_in_reset(struct gasket_dev *gasket_dev) ++{ ++ u32 val = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ ++ /* Masks rg_rst_gcb bit of SCU_CTRL_2 */ ++ return (val & SCU3_CUR_RST_GCB_BIT_MASK); ++} ++ ++/* Reset the hardware, then quit reset. Called on device open. */ ++static int apex_reset(struct gasket_dev *gasket_dev) ++{ ++ int ret; ++ ++ if (bypass_top_level) ++ return 0; ++ ++ if (!is_gcb_in_reset(gasket_dev)) { ++ /* We are not in reset - toggle the reset bit so as to force ++ * re-init of custom block ++ */ ++ dev_dbg(gasket_dev->dev, "%s: toggle reset\n", __func__); ++ ++ ret = apex_enter_reset(gasket_dev); ++ if (ret) ++ return ret; ++ } ++ ret = apex_quit_reset(gasket_dev); ++ ++ return ret; ++} ++ ++/* ++ * Check permissions for Apex ioctls. ++ * Returns true if the current user may execute this ioctl, and false otherwise. ++ */ ++static bool apex_ioctl_check_permissions(struct file *filp, uint cmd) ++{ ++ return !!(filp->f_mode & FMODE_WRITE); ++} ++ ++/* Gates or un-gates Apex clock. */ ++static long apex_clock_gating(struct gasket_dev *gasket_dev, ++ struct apex_gate_clock_ioctl __user *argp) ++{ ++ struct apex_gate_clock_ioctl ibuf; ++ ++ if (bypass_top_level || !allow_sw_clock_gating) ++ return 0; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(ibuf))) ++ return -EFAULT; ++ ++ dev_dbg(gasket_dev->dev, "%s %llu\n", __func__, ibuf.enable); ++ ++ if (ibuf.enable) { ++ /* Quiesce AXI, gate GCB clock. */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_AXI_QUIESCE, 0x1, 1, ++ 16); ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_GCB_CLOCK_GATE, 0x1, ++ 2, 18); ++ } else { ++ /* Un-gate GCB clock, un-quiesce AXI. */ ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_GCB_CLOCK_GATE, 0x0, ++ 2, 18); ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_AXI_QUIESCE, 0x0, 1, ++ 16); ++ } ++ return 0; ++} ++ ++/* apex_set_performance_expectation: Adjust clock rates for Apex. */ ++static long apex_set_performance_expectation( ++ struct gasket_dev *gasket_dev, ++ struct apex_performance_expectation_ioctl __user *argp) ++{ ++ struct apex_performance_expectation_ioctl ibuf; ++ uint32_t rg_gcb_clk_div = 0; ++ uint32_t rg_axi_clk_fixed = 0; ++ const int AXI_CLK_FIXED_SHIFT = 2; ++ const int MCU_CLK_FIXED_SHIFT = 3; ++ ++ // 8051 clock is fixed for PCIe, as it's not used at all. ++ const uint32_t rg_8051_clk_fixed = 1; ++ ++ if (bypass_top_level) ++ return 0; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(ibuf))) ++ return -EFAULT; ++ ++ switch (ibuf.performance) { ++ case APEX_PERFORMANCE_LOW: ++ rg_gcb_clk_div = 3; ++ rg_axi_clk_fixed = 0; ++ break; ++ ++ case APEX_PERFORMANCE_MED: ++ rg_gcb_clk_div = 2; ++ rg_axi_clk_fixed = 0; ++ break; ++ ++ case APEX_PERFORMANCE_HIGH: ++ rg_gcb_clk_div = 1; ++ rg_axi_clk_fixed = 0; ++ break; ++ ++ case APEX_PERFORMANCE_MAX: ++ rg_gcb_clk_div = 0; ++ rg_axi_clk_fixed = 0; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ /* ++ * Set clock rates for GCB, AXI, and 8051: ++ */ ++ gasket_read_modify_write_32( ++ gasket_dev, APEX_BAR_INDEX, APEX_BAR2_REG_SCU_3, ++ (rg_gcb_clk_div | (rg_axi_clk_fixed << AXI_CLK_FIXED_SHIFT) | (rg_8051_clk_fixed << MCU_CLK_FIXED_SHIFT)), ++ /*mask_width=*/4, /*mask_shift=*/28); ++ ++ return 0; ++} ++ ++/* Apex-specific ioctl handler. */ ++static long apex_ioctl(struct file *filp, uint cmd, void __user *argp) ++{ ++ struct gasket_dev *gasket_dev = filp->private_data; ++ ++ if (!apex_ioctl_check_permissions(filp, cmd)) ++ return -EPERM; ++ ++ switch (cmd) { ++ case APEX_IOCTL_GATE_CLOCK: ++ return apex_clock_gating(gasket_dev, argp); ++ case APEX_IOCTL_PERFORMANCE_EXPECTATION: ++ return apex_set_performance_expectation(gasket_dev, argp); ++ default: ++ return -ENOTTY; /* unknown command */ ++ } ++} ++ ++/* Linear fit optimized for 25C-100C */ ++static int adc_to_millic(int adc) ++{ ++ return (662 - adc) * 250 + 550; ++} ++ ++static int millic_to_adc(int millic) ++{ ++ return (550 - millic) / 250 + 662; ++} ++ ++/* Display driver sysfs entries. */ ++static ssize_t sysfs_show(struct device *device, struct device_attribute *attr, ++ char *buf) ++{ ++ int ret; ++ unsigned value, value2, value3, value4; ++ struct gasket_dev *gasket_dev; ++ struct apex_dev *apex_dev; ++ struct gasket_sysfs_attribute *gasket_attr; ++ enum sysfs_attribute_type type; ++ ++ gasket_dev = gasket_sysfs_get_device_data(device); ++ if (!gasket_dev) { ++ dev_err(device, "No Apex device sysfs mapping found\n"); ++ return -ENODEV; ++ } ++ ++ if (!gasket_dev->pci_dev || ++ !(apex_dev = pci_get_drvdata(gasket_dev->pci_dev))) { ++ dev_err(device, "Can't find apex_dev data\n"); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return -ENODEV; ++ } ++ ++ gasket_attr = gasket_sysfs_get_attr(device, attr); ++ if (!gasket_attr) { ++ dev_err(device, "No Apex device sysfs attr data found\n"); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return -ENODEV; ++ } ++ ++ type = (enum sysfs_attribute_type)gasket_attr->data.attr_type; ++ switch (type) { ++ case ATTR_KERNEL_HIB_PAGE_TABLE_SIZE: ++ ret = scnprintf(buf, PAGE_SIZE, "%u\n", ++ gasket_page_table_num_entries( ++ gasket_dev->page_table[0])); ++ break; ++ case ATTR_KERNEL_HIB_SIMPLE_PAGE_TABLE_SIZE: ++ ret = scnprintf(buf, PAGE_SIZE, "%u\n", ++ gasket_page_table_num_entries( ++ gasket_dev->page_table[0])); ++ break; ++ case ATTR_KERNEL_HIB_NUM_ACTIVE_PAGES: ++ ret = scnprintf(buf, PAGE_SIZE, "%u\n", ++ gasket_page_table_num_active_pages( ++ gasket_dev->page_table[0])); ++ break; ++ case ATTR_TEMP: ++ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_DC); ++ value = (value >> 16) & ((1 << 10) - 1); ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", adc_to_millic(value)); ++ break; ++ case ATTR_TEMP_WARN1: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ adc_to_millic(apex_dev->hw_temp_warn1_adc)); ++ break; ++ case ATTR_TEMP_WARN2: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ adc_to_millic(apex_dev->hw_temp_warn2_adc)); ++ break; ++ case ATTR_TEMP_WARN1_EN: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ apex_dev->hw_temp_warn1_en); ++ break; ++ case ATTR_TEMP_WARN2_EN: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ apex_dev->hw_temp_warn2_en); ++ break; ++ case ATTR_TEMP_TRIP0: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ adc_to_millic(apex_dev->adc_trip_points[0])); ++ break; ++ case ATTR_TEMP_TRIP1: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ adc_to_millic(apex_dev->adc_trip_points[1])); ++ break; ++ case ATTR_TEMP_TRIP2: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ adc_to_millic(apex_dev->adc_trip_points[2])); ++ break; ++ case ATTR_TEMP_POLL_INTERVAL: ++ ret = scnprintf(buf, PAGE_SIZE, "%i\n", ++ atomic_read(&apex_dev->temp_poll_interval)); ++ break; ++ case ATTR_UNIQUE_ID: ++ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_EFUSE_DC); ++ value2 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_EFUSE_E0); ++ value3 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_EFUSE_E4); ++ value4 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_EFUSE_E8); ++ ret = snprintf(buf, PAGE_SIZE, "%.8x%.8x%.8x%.8x\n", value4, ++ value3, value2, value); ++ break; ++ ++ default: ++ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n", ++ attr->attr.name); ++ ret = 0; ++ break; ++ } ++ ++ gasket_sysfs_put_attr(device, gasket_attr); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return ret; ++} ++ ++/* Set driver sysfs entries. */ ++static ssize_t sysfs_store(struct device *device, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ int ret = count, value; ++ struct gasket_dev *gasket_dev; ++ struct apex_dev *apex_dev; ++ struct gasket_sysfs_attribute *gasket_attr; ++ enum sysfs_attribute_type type; ++ ++ if (kstrtoint(buf, 10, &value)) ++ return -EINVAL; ++ ++ gasket_dev = gasket_sysfs_get_device_data(device); ++ if (!gasket_dev) { ++ dev_err(device, "No Apex device sysfs mapping found\n"); ++ return -ENODEV; ++ } ++ ++ if (!gasket_dev->pci_dev || ++ !(apex_dev = pci_get_drvdata(gasket_dev->pci_dev))) { ++ dev_err(device, "Can't find apex_dev data\n"); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return -ENODEV; ++ } ++ ++ gasket_attr = gasket_sysfs_get_attr(device, attr); ++ if (!gasket_attr) { ++ dev_err(device, "No Apex device sysfs attr data found\n"); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return -ENODEV; ++ } ++ ++ type = (enum sysfs_attribute_type)gasket_attr->data.attr_type; ++ switch (type) { ++ case ATTR_TEMP_WARN1: ++ value = millic_to_adc(value); ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D4, value, 10, ++ 16); ++ apex_dev->hw_temp_warn1_adc = value; ++ break; ++ case ATTR_TEMP_WARN2: ++ value = millic_to_adc(value); ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D8, value, 10, ++ 16); ++ apex_dev->hw_temp_warn2_adc = value; ++ break; ++ case ATTR_TEMP_WARN1_EN: ++ value = value > 0 ? 1 : 0; ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D4, value, 1, ++ 31); ++ apex_dev->hw_temp_warn1_en = !!value; ++ break; ++ case ATTR_TEMP_WARN2_EN: ++ value = value > 0 ? 1 : 0; ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D8, value, 1, ++ 31); ++ apex_dev->hw_temp_warn2_en = !!value; ++ break; ++ case ATTR_TEMP_TRIP0: ++ value = millic_to_adc(value); ++ /* Note: that adc values should be in descending order */ ++ if (value >= apex_dev->adc_trip_points[1]) { ++ apex_dev->adc_trip_points[0] = value; ++ } else ret = -EINVAL; ++ break; ++ case ATTR_TEMP_TRIP1: ++ value = millic_to_adc(value); ++ if (value <= apex_dev->adc_trip_points[0] && ++ value >= apex_dev->adc_trip_points[2]) { ++ apex_dev->adc_trip_points[1] = value; ++ } else ret = -EINVAL; ++ break; ++ case ATTR_TEMP_TRIP2: ++ value = millic_to_adc(value); ++ if (value <= apex_dev->adc_trip_points[1]) { ++ apex_dev->adc_trip_points[2] = value; ++ } else ret = -EINVAL; ++ break; ++ case ATTR_TEMP_POLL_INTERVAL: ++ cancel_delayed_work_sync(&apex_dev->check_temperature_work); ++ atomic_set(&apex_dev->temp_poll_interval, value); ++ if (value > 0) ++ schedule_delayed_work(&apex_dev->check_temperature_work, ++ msecs_to_jiffies(value)); ++ ++ break; ++ default: ++ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n", ++ attr->attr.name); ++ ret = 0; ++ break; ++ } ++ ++ gasket_sysfs_put_attr(device, gasket_attr); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return ret; ++} ++ ++static struct gasket_sysfs_attribute apex_sysfs_attrs[] = { ++ GASKET_SYSFS_RO(node_0_page_table_entries, sysfs_show, ++ ATTR_KERNEL_HIB_PAGE_TABLE_SIZE), ++ GASKET_SYSFS_RO(node_0_simple_page_table_entries, sysfs_show, ++ ATTR_KERNEL_HIB_SIMPLE_PAGE_TABLE_SIZE), ++ GASKET_SYSFS_RO(node_0_num_mapped_pages, sysfs_show, ++ ATTR_KERNEL_HIB_NUM_ACTIVE_PAGES), ++ GASKET_SYSFS_RO(temp, sysfs_show, ATTR_TEMP), ++ GASKET_SYSFS_RW(hw_temp_warn1, sysfs_show, sysfs_store, ++ ATTR_TEMP_WARN1), ++ GASKET_SYSFS_RW(hw_temp_warn1_en, sysfs_show, sysfs_store, ++ ATTR_TEMP_WARN1_EN), ++ GASKET_SYSFS_RW(hw_temp_warn2, sysfs_show, sysfs_store, ++ ATTR_TEMP_WARN2), ++ GASKET_SYSFS_RW(hw_temp_warn2_en, sysfs_show, sysfs_store, ++ ATTR_TEMP_WARN2_EN), ++ GASKET_SYSFS_RW(trip_point0_temp, sysfs_show, sysfs_store, ++ ATTR_TEMP_TRIP0), ++ GASKET_SYSFS_RW(trip_point1_temp, sysfs_show, sysfs_store, ++ ATTR_TEMP_TRIP1), ++ GASKET_SYSFS_RW(trip_point2_temp, sysfs_show, sysfs_store, ++ ATTR_TEMP_TRIP2), ++ GASKET_SYSFS_RW(temp_poll_interval, sysfs_show, sysfs_store, ++ ATTR_TEMP_POLL_INTERVAL), ++ GASKET_SYSFS_RO(unique_id, sysfs_show, ATTR_UNIQUE_ID), ++ GASKET_END_OF_ATTR_ARRAY ++}; ++ ++/* Stores kernel module parameters to device specific data buffer */ ++static void apply_module_params(struct apex_dev *apex_dev) { ++ kernel_param_lock(THIS_MODULE); ++ ++ /* use defaults if trip point temperatures are not in ascending order */ ++ if (trip_point0_temp > trip_point1_temp || ++ trip_point1_temp > trip_point2_temp) { ++ dev_warn(apex_dev->gasket_dev_ptr->dev, ++ "Invalid module parameters for temperature trip points" ++ ", using defaults\n"); ++ trip_point0_temp = DEFAULT_TRIP_POINT0_TEMP; ++ trip_point1_temp = DEFAULT_TRIP_POINT1_TEMP; ++ trip_point2_temp = DEFAULT_TRIP_POINT2_TEMP; ++ } ++ ++ apex_dev->adc_trip_points[0] = millic_to_adc(trip_point0_temp); ++ apex_dev->adc_trip_points[1] = millic_to_adc(trip_point1_temp); ++ apex_dev->adc_trip_points[2] = millic_to_adc(trip_point2_temp); ++ atomic_set(&apex_dev->temp_poll_interval, temp_poll_interval); ++ ++ apex_dev->hw_temp_warn1_adc = millic_to_adc(hw_temp_warn1); ++ apex_dev->hw_temp_warn2_adc = millic_to_adc(hw_temp_warn2); ++ apex_dev->hw_temp_warn1_en = hw_temp_warn1_en; ++ apex_dev->hw_temp_warn2_en = hw_temp_warn2_en; ++ ++ kernel_param_unlock(THIS_MODULE); ++} ++ ++/* Applies hw temp warning settings to device */ ++static void program_hw_temp_warnings(struct apex_dev *apex_dev) { ++ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D4, ++ apex_dev->hw_temp_warn1_adc, 10, 16); ++ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D8, ++ apex_dev->hw_temp_warn2_adc, 10, 16); ++ if (apex_dev->hw_temp_warn1_en) ++ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr, ++ APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D4, 1, 1, 31); ++ ++ if (apex_dev->hw_temp_warn2_en) ++ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr, ++ APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D8, 1, 1, 31); ++} ++ ++static void enable_thermal_sensing(struct gasket_dev *gasket_dev) { ++ // Enable thermal sensor clocks ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D0, 0x1, 1, 7); ++ ++ // Enable thermal sensor (ENAD ENVR ENBG) ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_D8, 0x7, 3, 0); ++ ++ // Enable OMC thermal sensor controller ++ // This bit should be asserted 100 us after ENAD ENVR ENBG ++ schedule_timeout(usecs_to_jiffies(100)); ++ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_DC, 0x1, 1, 0); ++} ++ ++static void check_temperature_work_handler(struct work_struct *work) { ++ int i, temp_poll_interval; ++ u32 adc_temp, clk_div, tmp; ++ const u32 mask = ((1 << 2) - 1) << 28; ++ struct apex_dev *apex_dev = ++ container_of(work, struct apex_dev, ++ check_temperature_work.work); ++ struct gasket_dev *gasket_dev = apex_dev->gasket_dev_ptr; ++ ++ mutex_lock(&gasket_dev->mutex); ++ ++ /* Read current temperature */ ++ adc_temp = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_OMC0_DC); ++ adc_temp = (adc_temp >> 16) & ((1 << 10) - 1); ++ ++ /* Find closest trip point ++ Note: that adc values are in descending order */ ++ for (i = ARRAY_SIZE(apex_dev->adc_trip_points) - 1; i >= 0; --i) { ++ if (adc_temp <= apex_dev->adc_trip_points[i]) ++ break; ++ } ++ /* Compute divider value and shift into appropriate bit location */ ++ clk_div = (i + 1) << 28; ++ ++ /* Modify gcb clk divider if it's different from current one */ ++ tmp = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ if (clk_div != (tmp & mask)) { ++ tmp = (tmp & ~mask) | clk_div; ++ gasket_dev_write_32(gasket_dev, tmp, APEX_BAR_INDEX, ++ APEX_BAR2_REG_SCU_3); ++ dev_warn(gasket_dev->dev, ++ "Apex performance %sthrottled due to temperature\n", ++ i == -1 ? "not " : ""); ++ } ++ ++ mutex_unlock(&gasket_dev->mutex); ++ ++ temp_poll_interval = atomic_read(&apex_dev->temp_poll_interval); ++ if (temp_poll_interval > 0) ++ schedule_delayed_work(&apex_dev->check_temperature_work, ++ msecs_to_jiffies(temp_poll_interval)); ++} ++ ++/* On device open, perform a core reinit reset. */ ++static int apex_device_open_cb(struct gasket_dev *gasket_dev) ++{ ++ return gasket_reset_nolock(gasket_dev); ++} ++ ++static const struct pci_device_id apex_pci_ids[] = { ++ { PCI_DEVICE(APEX_PCI_VENDOR_ID, APEX_PCI_DEVICE_ID) }, { 0 } ++}; ++ ++static void apex_pci_fixup_class(struct pci_dev *pdev) ++{ ++ pdev->class = (PCI_CLASS_SYSTEM_OTHER << 8) | pdev->class; ++} ++DECLARE_PCI_FIXUP_CLASS_HEADER(APEX_PCI_VENDOR_ID, APEX_PCI_DEVICE_ID, ++ PCI_ANY_ID, 8, apex_pci_fixup_class); ++ ++static int apex_pci_probe(struct pci_dev *pci_dev, ++ const struct pci_device_id *id) ++{ ++ int ret, temp_poll_interval; ++ ulong page_table_ready, msix_table_ready; ++ int retries = 0; ++ struct gasket_dev *gasket_dev; ++ struct apex_dev *apex_dev; ++ ++ ret = pci_enable_device(pci_dev); ++#ifdef MODULE ++ if (ret) { ++ apex_pci_fixup_class(pci_dev); ++ pci_bus_assign_resources(pci_dev->bus); ++ ret = pci_enable_device(pci_dev); ++ } ++#endif ++ if (ret) { ++ dev_err(&pci_dev->dev, "error enabling PCI device\n"); ++ return ret; ++ } ++ ++ pci_set_master(pci_dev); ++ ++ ret = gasket_pci_add_device(pci_dev, &gasket_dev); ++ if (ret) { ++ dev_err(&pci_dev->dev, "error adding gasket device\n"); ++ pci_disable_device(pci_dev); ++ return ret; ++ } ++ ++ apex_dev = kzalloc(sizeof(*apex_dev), GFP_KERNEL); ++ if (!apex_dev) { ++ dev_err(&pci_dev->dev, "no memory for device\n"); ++ ret = -ENOMEM; ++ goto remove_device; ++ } ++ ++ INIT_DELAYED_WORK(&apex_dev->check_temperature_work, ++ check_temperature_work_handler); ++ apex_dev->gasket_dev_ptr = gasket_dev; ++ apply_module_params(apex_dev); ++ program_hw_temp_warnings(apex_dev); ++ pci_set_drvdata(pci_dev, apex_dev); ++ apex_reset(gasket_dev); ++ ++ while (retries < APEX_RESET_RETRY) { ++ page_table_ready = ++ gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_INIT); ++ msix_table_ready = ++ gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX, ++ APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE_INIT); ++ if (page_table_ready && msix_table_ready) ++ break; ++ schedule_timeout(msecs_to_jiffies(APEX_RESET_DELAY)); ++ retries++; ++ } ++ ++ if (retries == APEX_RESET_RETRY) { ++ if (!page_table_ready) ++ dev_err(gasket_dev->dev, "Page table init timed out\n"); ++ if (!msix_table_ready) ++ dev_err(gasket_dev->dev, "MSI-X table init timed out\n"); ++ ret = -ETIMEDOUT; ++ goto remove_device; ++ } ++ ++ enable_thermal_sensing(gasket_dev); ++ ++ ret = gasket_sysfs_create_entries(gasket_dev->dev_info.device, ++ apex_sysfs_attrs); ++ if (ret) ++ dev_err(&pci_dev->dev, "error creating device sysfs entries\n"); ++ ++ ret = gasket_enable_device(gasket_dev); ++ if (ret) { ++ dev_err(&pci_dev->dev, "error enabling gasket device\n"); ++ goto remove_device; ++ } ++ ++ /* Place device in low power mode until opened */ ++ if (allow_power_save) ++ apex_enter_reset(gasket_dev); ++ ++ /* Enable thermal polling */ ++ temp_poll_interval = atomic_read(&apex_dev->temp_poll_interval); ++ if (temp_poll_interval > 0) ++ schedule_delayed_work(&apex_dev->check_temperature_work, ++ msecs_to_jiffies(temp_poll_interval)); ++ return 0; ++ ++remove_device: ++ gasket_pci_remove_device(pci_dev); ++ pci_disable_device(pci_dev); ++ kfree(apex_dev); ++ return ret; ++} ++ ++static void apex_pci_remove(struct pci_dev *pci_dev) ++{ ++ struct apex_dev *apex_dev = pci_get_drvdata(pci_dev); ++ struct gasket_dev *gasket_dev; ++ ++ if (!apex_dev) { ++ dev_err(&pci_dev->dev, "NULL apex_dev\n"); ++ goto remove_device; ++ } ++ gasket_dev = apex_dev->gasket_dev_ptr; ++ ++ cancel_delayed_work_sync(&apex_dev->check_temperature_work); ++ kfree(apex_dev); ++ ++ gasket_disable_device(gasket_dev); ++remove_device: ++ gasket_pci_remove_device(pci_dev); ++ pci_disable_device(pci_dev); ++} ++ ++static int apex_pci_suspend(struct pci_dev *pci_dev, pm_message_t state) { ++ struct apex_dev *apex_dev = pci_get_drvdata(pci_dev); ++ struct gasket_dev *gasket_dev; ++ ++ if (!apex_dev) { ++ dev_err_once(&pci_dev->dev, "NULL apex_dev\n"); ++ return -ENODEV; ++ } ++ ++ // Tear down MSI-x interrupts before suspending. ++ gasket_dev = apex_dev->gasket_dev_ptr; ++ gasket_interrupt_msix_cleanup(gasket_dev->interrupt_data); ++ return 0; ++} ++ ++static int apex_pci_resume(struct pci_dev *pci_dev) ++{ ++ struct apex_dev *apex_dev = pci_get_drvdata(pci_dev); ++ struct gasket_dev *gasket_dev; ++ ++ if (!apex_dev) { ++ dev_err_once(&pci_dev->dev, "NULL apex_dev\n"); ++ return -ENODEV; ++ } ++ gasket_dev = apex_dev->gasket_dev_ptr; ++ ++ gasket_interrupt_reinit(gasket_dev); ++ apex_reset(gasket_dev); ++ program_hw_temp_warnings(apex_dev); ++ enable_thermal_sensing(gasket_dev); ++ ++ /* Place device in low power mode until opened */ ++ if (allow_power_save) ++ apex_enter_reset(gasket_dev); ++ ++ return 0; ++} ++ ++static struct gasket_driver_desc apex_desc = { ++ .name = "apex", ++ .driver_version = APEX_DRIVER_VERSION, ++ .major = 120, ++ .minor = 0, ++ .module = THIS_MODULE, ++ .pci_id_table = apex_pci_ids, ++ ++ .num_page_tables = NUM_NODES, ++ .page_table_bar_index = APEX_BAR_INDEX, ++ .page_table_configs = apex_page_table_configs, ++ .page_table_extended_bit = APEX_EXTENDED_SHIFT, ++ ++ .bar_descriptions = { ++ GASKET_UNUSED_BAR, ++ GASKET_UNUSED_BAR, ++ { APEX_BAR_BYTES, (VM_WRITE | VM_READ), APEX_BAR_OFFSET, ++ NUM_REGIONS, mappable_regions, PCI_BAR }, ++ GASKET_UNUSED_BAR, ++ GASKET_UNUSED_BAR, ++ GASKET_UNUSED_BAR, ++ }, ++ .coherent_buffer_description = { ++ APEX_CH_MEM_BYTES, ++ (VM_WRITE | VM_READ), ++ APEX_CM_OFFSET, ++ }, ++ .interrupt_type = PCI_MSIX, ++ .interrupt_bar_index = APEX_BAR_INDEX, ++ .num_interrupts = APEX_INTERRUPT_COUNT, ++ .interrupts = apex_interrupts, ++ .interrupt_pack_width = 7, ++ ++ .device_open_cb = apex_device_open_cb, ++ .device_close_cb = apex_device_cleanup, ++ ++ .ioctl_handler_cb = apex_ioctl, ++ .device_status_cb = apex_get_status, ++ .hardware_revision_cb = NULL, ++ .device_reset_cb = apex_reset, ++}; ++ ++static struct pci_driver apex_pci_driver = { ++ .name = "apex", ++ .probe = apex_pci_probe, ++ .remove = apex_pci_remove, ++#ifdef CONFIG_PM_SLEEP ++ .suspend = apex_pci_suspend, ++ .resume = apex_pci_resume, ++#endif ++ .id_table = apex_pci_ids, ++}; ++ ++static int __init apex_init(void) ++{ ++ int ret; ++ ++ ret = gasket_register_device(&apex_desc); ++ if (ret) ++ return ret; ++ ret = pci_register_driver(&apex_pci_driver); ++ if (ret) ++ gasket_unregister_device(&apex_desc); ++ return ret; ++} ++ ++static void apex_exit(void) ++{ ++ pci_unregister_driver(&apex_pci_driver); ++ gasket_unregister_device(&apex_desc); ++} ++MODULE_DESCRIPTION("Google Apex driver"); ++MODULE_VERSION(APEX_DRIVER_VERSION); ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("John Joseph "); ++MODULE_DEVICE_TABLE(pci, apex_pci_ids); ++module_init(apex_init); ++module_exit(apex_exit); +diff '--color=auto' -urN a/drivers/staging/gasket-driver/apex.h b/drivers/staging/gasket-driver/apex.h +--- a/drivers/staging/gasket-driver/apex.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/apex.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,45 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Apex kernel-userspace interface definitions. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++#ifndef __APEX_H__ ++#define __APEX_H__ ++ ++#include ++ ++/* Clock Gating ioctl. */ ++struct apex_gate_clock_ioctl { ++ /* Enter or leave clock gated state. */ ++ u64 enable; ++ ++ /* If set, enter clock gating state, regardless of custom block's ++ * internal idle state ++ */ ++ u64 force_idle; ++}; ++ ++/* Performance expectation ioctl. */ ++enum apex_performance_expectation { ++ APEX_PERFORMANCE_LOW = 0, ++ APEX_PERFORMANCE_MED = 1, ++ APEX_PERFORMANCE_HIGH = 2, ++ APEX_PERFORMANCE_MAX = 3, ++}; ++ ++struct apex_performance_expectation_ioctl { ++ /* Expected performance from apex. */ ++ uint32_t performance; ++}; ++ ++/* Base number for all Apex-common IOCTLs */ ++#define APEX_IOCTL_BASE 0x7F ++ ++/* Enable/Disable clock gating. */ ++#define APEX_IOCTL_GATE_CLOCK \ ++ _IOW(APEX_IOCTL_BASE, 0, struct apex_gate_clock_ioctl) ++ ++#define APEX_IOCTL_PERFORMANCE_EXPECTATION _IOW(APEX_IOCTL_BASE, 1, struct apex_performance_expectation_ioctl) ++ ++#endif /* __APEX_H__ */ +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_constants.h b/drivers/staging/gasket-driver/gasket_constants.h +--- a/drivers/staging/gasket-driver/gasket_constants.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_constants.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2018 Google, Inc. */ ++#ifndef __GASKET_CONSTANTS_H__ ++#define __GASKET_CONSTANTS_H__ ++ ++#define GASKET_FRAMEWORK_VERSION "1.1.4" ++ ++/* ++ * The maximum number of simultaneous device types supported by the framework. ++ */ ++#define GASKET_FRAMEWORK_DESC_MAX 2 ++ ++/* The maximum devices per each type. */ ++#define GASKET_DEV_MAX 256 ++ ++/* The number of supported (and possible) PCI BARs. */ ++#define GASKET_NUM_BARS 6 ++ ++/* The number of supported Gasket page tables per device. */ ++#define GASKET_MAX_NUM_PAGE_TABLES 1 ++ ++/* Maximum length of device names (driver name + minor number suffix + NULL). */ ++#define GASKET_NAME_MAX 32 ++ ++/* Device status enumeration. */ ++enum gasket_status { ++ /* ++ * A device is DEAD if it has not been initialized or has had an error. ++ */ ++ GASKET_STATUS_DEAD = 0, ++ /* ++ * A device is LAMED if the hardware is healthy but the kernel was ++ * unable to enable some functionality (e.g. interrupts). ++ */ ++ GASKET_STATUS_LAMED, ++ ++ /* A device is ALIVE if it is ready for operation. */ ++ GASKET_STATUS_ALIVE, ++ ++ /* ++ * This status is set when the driver is exiting and waiting for all ++ * handles to be closed. ++ */ ++ GASKET_STATUS_DRIVER_EXIT, ++}; ++ ++#endif +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_core.c b/drivers/staging/gasket-driver/gasket_core.c +--- a/drivers/staging/gasket-driver/gasket_core.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_core.c 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,1936 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Gasket generic driver framework. This file contains the implementation ++ * for the Gasket generic driver framework - the functionality that is common ++ * across Gasket devices. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include "gasket_core.h" ++ ++#include "gasket_interrupt.h" ++#include "gasket_ioctl.h" ++#include "gasket_page_table.h" ++#include "gasket_sysfs.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef GASKET_KERNEL_TRACE_SUPPORT ++#define CREATE_TRACE_POINTS ++#include ++#else ++#define trace_gasket_mmap_exit(x) ++#define trace_gasket_mmap_entry(x, ...) ++#endif ++ ++/* ++ * "Private" members of gasket_driver_desc. ++ * ++ * Contains internal per-device type tracking data, i.e., data not appropriate ++ * as part of the public interface for the generic framework. ++ */ ++struct gasket_internal_desc { ++ /* Device-specific-driver-provided configuration information. */ ++ const struct gasket_driver_desc *driver_desc; ++ ++ /* Protects access to per-driver data (i.e. this structure). */ ++ struct mutex mutex; ++ ++ /* Kernel-internal device class. */ ++ struct class *class; ++ ++ /* Instantiated / present devices of this type. */ ++ struct gasket_dev *devs[GASKET_DEV_MAX]; ++}; ++ ++/* do_map_region() needs be able to return more than just true/false. */ ++enum do_map_region_status { ++ /* The region was successfully mapped. */ ++ DO_MAP_REGION_SUCCESS, ++ ++ /* Attempted to map region and failed. */ ++ DO_MAP_REGION_FAILURE, ++ ++ /* The requested region to map was not part of a mappable region. */ ++ DO_MAP_REGION_INVALID, ++}; ++ ++/* Global data definitions. */ ++/* Mutex - only for framework-wide data. Other data should be protected by ++ * finer-grained locks. ++ */ ++static DEFINE_MUTEX(g_mutex); ++ ++/* List of all registered device descriptions & their supporting data. */ ++static struct gasket_internal_desc g_descs[GASKET_FRAMEWORK_DESC_MAX]; ++ ++/* Mapping of statuses to human-readable strings. Must end with {0,NULL}. */ ++static const struct gasket_num_name gasket_status_name_table[] = { ++ { GASKET_STATUS_DEAD, "DEAD" }, ++ { GASKET_STATUS_ALIVE, "ALIVE" }, ++ { GASKET_STATUS_LAMED, "LAMED" }, ++ { GASKET_STATUS_DRIVER_EXIT, "DRIVER_EXITING" }, ++ { 0, NULL }, ++}; ++ ++/* Enumeration of the automatic Gasket framework sysfs nodes. */ ++enum gasket_sysfs_attribute_type { ++ ATTR_BAR_OFFSETS, ++ ATTR_BAR_SIZES, ++ ATTR_DRIVER_VERSION, ++ ATTR_FRAMEWORK_VERSION, ++ ATTR_DEVICE_TYPE, ++ ATTR_HARDWARE_REVISION, ++ ATTR_PCI_ADDRESS, ++ ATTR_STATUS, ++ ATTR_IS_DEVICE_OWNED, ++ ATTR_DEVICE_OWNER, ++ ATTR_WRITE_OPEN_COUNT, ++ ATTR_RESET_COUNT, ++ ATTR_USER_MEM_RANGES ++}; ++ ++/* On some arm64 systems pcie dma controller can only access lower 4GB of ++ * addresses. Unfortunately vendor BSP isn't providing any means of determining ++ * this limitation and there're no errors reported if access to higher addresses ++ * if being done. This parameter allows to workaround this issue by pretending ++ * that our device only supports 32 bit addresses. This in turn will cause ++ * dma driver to use shadow buffers located in low 32 bit address space. ++ */ ++static int dma_bit_mask = 64; ++module_param(dma_bit_mask, int, 0644); ++ ++/* Perform a standard Gasket callback. */ ++static inline int ++check_and_invoke_callback(struct gasket_dev *gasket_dev, ++ int (*cb_function)(struct gasket_dev *)) ++{ ++ int ret = 0; ++ ++ if (cb_function) { ++ mutex_lock(&gasket_dev->mutex); ++ ret = cb_function(gasket_dev); ++ mutex_unlock(&gasket_dev->mutex); ++ } ++ return ret; ++} ++ ++/* Perform a standard Gasket callback without grabbing gasket_dev->mutex. */ ++static inline int ++gasket_check_and_invoke_callback_nolock(struct gasket_dev *gasket_dev, ++ int (*cb_function)(struct gasket_dev *)) ++{ ++ int ret = 0; ++ ++ if (cb_function) ++ ret = cb_function(gasket_dev); ++ return ret; ++} ++ ++/* ++ * Return nonzero if the gasket_cdev_info is owned by the current thread group ++ * ID. ++ */ ++static int gasket_owned_by_current_tgid(struct gasket_cdev_info *info) ++{ ++ return (info->ownership.is_owned && ++ (info->ownership.owner == current->tgid)); ++} ++ ++/* ++ * Find the next free gasket_internal_dev slot. ++ * ++ * Returns the located slot number on success or a negative number on failure. ++ */ ++static int gasket_find_dev_slot(struct gasket_internal_desc *internal_desc, ++ const char *kobj_name) ++{ ++ int i; ++ ++ mutex_lock(&internal_desc->mutex); ++ ++ /* Search for a previous instance of this device. */ ++ for (i = 0; i < GASKET_DEV_MAX; i++) { ++ if (internal_desc->devs[i] && ++ strcmp(internal_desc->devs[i]->kobj_name, kobj_name) == 0) { ++ pr_err("Duplicate device %s\n", kobj_name); ++ mutex_unlock(&internal_desc->mutex); ++ return -EBUSY; ++ } ++ } ++ ++ /* Find a free device slot. */ ++ for (i = 0; i < GASKET_DEV_MAX; i++) { ++ if (!internal_desc->devs[i]) ++ break; ++ } ++ ++ if (i == GASKET_DEV_MAX) { ++ pr_err("Too many registered devices; max %d\n", GASKET_DEV_MAX); ++ mutex_unlock(&internal_desc->mutex); ++ return -EBUSY; ++ } ++ ++ mutex_unlock(&internal_desc->mutex); ++ return i; ++} ++ ++/* ++ * Allocate and initialize a Gasket device structure, add the device to the ++ * device list. ++ * ++ * Returns 0 if successful, a negative error code otherwise. ++ */ ++static int gasket_alloc_dev(struct gasket_internal_desc *internal_desc, ++ struct device *parent, struct gasket_dev **pdev) ++{ ++ int dev_idx; ++ const struct gasket_driver_desc *driver_desc = ++ internal_desc->driver_desc; ++ struct gasket_dev *gasket_dev; ++ struct gasket_cdev_info *dev_info; ++ const char *parent_name = dev_name(parent); ++ ++ pr_debug("Allocating a Gasket device, parent %s.\n", parent_name); ++ ++ *pdev = NULL; ++ ++ dev_idx = gasket_find_dev_slot(internal_desc, parent_name); ++ if (dev_idx < 0) ++ return dev_idx; ++ ++ gasket_dev = *pdev = kzalloc(sizeof(*gasket_dev), GFP_KERNEL); ++ if (!gasket_dev) { ++ pr_err("no memory for device, parent %s\n", parent_name); ++ return -ENOMEM; ++ } ++ internal_desc->devs[dev_idx] = gasket_dev; ++ ++ mutex_init(&gasket_dev->mutex); ++ ++ gasket_dev->internal_desc = internal_desc; ++ gasket_dev->dev_idx = dev_idx; ++ snprintf(gasket_dev->kobj_name, GASKET_NAME_MAX, "%s", parent_name); ++ gasket_dev->dev = get_device(parent); ++ gasket_dev->dma_dev = get_device(parent); ++ /* gasket_bar_data is uninitialized. */ ++ gasket_dev->num_page_tables = driver_desc->num_page_tables; ++ /* max_page_table_size and *page table are uninit'ed */ ++ /* interrupt_data is not initialized. */ ++ /* status is 0, or GASKET_STATUS_DEAD */ ++ ++ dev_info = &gasket_dev->dev_info; ++ snprintf(dev_info->name, GASKET_NAME_MAX, "%s_%u", driver_desc->name, ++ gasket_dev->dev_idx); ++ dev_info->devt = ++ MKDEV(driver_desc->major, driver_desc->minor + ++ gasket_dev->dev_idx); ++ dev_info->device = ++ device_create(internal_desc->class, parent, dev_info->devt, ++ gasket_dev, dev_info->name); ++ ++ /* cdev has not yet been added; cdev_added is 0 */ ++ dev_info->gasket_dev_ptr = gasket_dev; ++ /* ownership is all 0, indicating no owner or opens. */ ++ ++ return 0; ++} ++ ++/* Free a Gasket device. */ ++static void gasket_free_dev(struct gasket_dev *gasket_dev) ++{ ++ struct gasket_internal_desc *internal_desc = gasket_dev->internal_desc; ++ ++ mutex_lock(&internal_desc->mutex); ++ internal_desc->devs[gasket_dev->dev_idx] = NULL; ++ mutex_unlock(&internal_desc->mutex); ++ put_device(gasket_dev->dev); ++ put_device(gasket_dev->dma_dev); ++ kfree(gasket_dev); ++} ++ ++/* ++ * Maps the specified bar into kernel space. ++ * ++ * Returns 0 on success, a negative error code otherwise. ++ * A zero-sized BAR will not be mapped, but is not an error. ++ */ ++static int gasket_map_pci_bar(struct gasket_dev *gasket_dev, int bar_num) ++{ ++ struct gasket_internal_desc *internal_desc = gasket_dev->internal_desc; ++ const struct gasket_driver_desc *driver_desc = ++ internal_desc->driver_desc; ++ ulong desc_bytes = driver_desc->bar_descriptions[bar_num].size; ++ int ret; ++ ++ if (desc_bytes == 0) ++ return 0; ++ ++ if (driver_desc->bar_descriptions[bar_num].type != PCI_BAR) { ++ /* not PCI: skip this entry */ ++ return 0; ++ } ++ /* ++ * pci_resource_start and pci_resource_len return a "resource_size_t", ++ * which is safely castable to ulong (which itself is the arg to ++ * request_mem_region). ++ */ ++ gasket_dev->bar_data[bar_num].phys_base = ++ (ulong)pci_resource_start(gasket_dev->pci_dev, bar_num); ++ if (!gasket_dev->bar_data[bar_num].phys_base) { ++ dev_err(gasket_dev->dev, "Cannot get BAR%u base address\n", ++ bar_num); ++ return -EINVAL; ++ } ++ ++ gasket_dev->bar_data[bar_num].length_bytes = ++ (ulong)pci_resource_len(gasket_dev->pci_dev, bar_num); ++ if (gasket_dev->bar_data[bar_num].length_bytes < desc_bytes) { ++ dev_err(gasket_dev->dev, ++ "PCI BAR %u space is too small: %lu; expected >= %lu\n", ++ bar_num, gasket_dev->bar_data[bar_num].length_bytes, ++ desc_bytes); ++ return -ENOMEM; ++ } ++ ++ if (!request_mem_region(gasket_dev->bar_data[bar_num].phys_base, ++ gasket_dev->bar_data[bar_num].length_bytes, ++ gasket_dev->dev_info.name)) { ++ dev_err(gasket_dev->dev, ++ "Cannot get BAR %d memory region %p\n", ++ bar_num, &gasket_dev->pci_dev->resource[bar_num]); ++ return -EINVAL; ++ } ++ ++ gasket_dev->bar_data[bar_num].virt_base = ++ ioremap(gasket_dev->bar_data[bar_num].phys_base, ++ gasket_dev->bar_data[bar_num].length_bytes); ++ if (!gasket_dev->bar_data[bar_num].virt_base) { ++ dev_err(gasket_dev->dev, ++ "Cannot remap BAR %d memory region %p\n", ++ bar_num, &gasket_dev->pci_dev->resource[bar_num]); ++ ret = -ENOMEM; ++ goto fail; ++ } ++ ++ dma_set_mask(&gasket_dev->pci_dev->dev, DMA_BIT_MASK(dma_bit_mask)); ++ dma_set_coherent_mask(&gasket_dev->pci_dev->dev, ++ DMA_BIT_MASK(dma_bit_mask)); ++ ++ return 0; ++ ++fail: ++ iounmap(gasket_dev->bar_data[bar_num].virt_base); ++ release_mem_region(gasket_dev->bar_data[bar_num].phys_base, ++ gasket_dev->bar_data[bar_num].length_bytes); ++ return ret; ++} ++ ++/* ++ * Releases PCI BAR mapping. ++ * ++ * A zero-sized or not-mapped BAR will not be unmapped, but is not an error. ++ */ ++static void gasket_unmap_pci_bar(struct gasket_dev *dev, int bar_num) ++{ ++ ulong base, bytes; ++ struct gasket_internal_desc *internal_desc = dev->internal_desc; ++ const struct gasket_driver_desc *driver_desc = ++ internal_desc->driver_desc; ++ ++ if (driver_desc->bar_descriptions[bar_num].size == 0 || ++ !dev->bar_data[bar_num].virt_base) ++ return; ++ ++ if (driver_desc->bar_descriptions[bar_num].type != PCI_BAR) ++ return; ++ ++ iounmap(dev->bar_data[bar_num].virt_base); ++ dev->bar_data[bar_num].virt_base = NULL; ++ ++ base = pci_resource_start(dev->pci_dev, bar_num); ++ if (!base) { ++ dev_err(dev->dev, "cannot get PCI BAR%u base address\n", ++ bar_num); ++ return; ++ } ++ ++ bytes = pci_resource_len(dev->pci_dev, bar_num); ++ release_mem_region(base, bytes); ++} ++ ++/* ++ * Setup PCI memory mapping for the specified device. ++ * ++ * Reads the BAR registers and sets up pointers to the device's memory mapped ++ * IO space. ++ * ++ * Returns 0 on success and a negative value otherwise. ++ */ ++static int gasket_setup_pci(struct pci_dev *pci_dev, ++ struct gasket_dev *gasket_dev) ++{ ++ int i, mapped_bars, ret; ++ ++ for (i = 0; i < GASKET_NUM_BARS; i++) { ++ ret = gasket_map_pci_bar(gasket_dev, i); ++ if (ret) { ++ mapped_bars = i; ++ goto fail; ++ } ++ } ++ ++ return 0; ++ ++fail: ++ for (i = 0; i < mapped_bars; i++) ++ gasket_unmap_pci_bar(gasket_dev, i); ++ ++ return -ENOMEM; ++} ++ ++/* Unmaps memory for the specified device. */ ++static void gasket_cleanup_pci(struct gasket_dev *gasket_dev) ++{ ++ int i; ++ ++ for (i = 0; i < GASKET_NUM_BARS; i++) ++ gasket_unmap_pci_bar(gasket_dev, i); ++} ++ ++/* Determine the health of the Gasket device. */ ++static int gasket_get_hw_status(struct gasket_dev *gasket_dev) ++{ ++ int status; ++ int i; ++ const struct gasket_driver_desc *driver_desc = ++ gasket_dev->internal_desc->driver_desc; ++ ++ status = gasket_check_and_invoke_callback_nolock(gasket_dev, ++ driver_desc->device_status_cb); ++ if (status != GASKET_STATUS_ALIVE) { ++ dev_dbg(gasket_dev->dev, "Hardware reported status %d.\n", ++ status); ++ return status; ++ } ++ ++ status = gasket_interrupt_system_status(gasket_dev); ++ if (status != GASKET_STATUS_ALIVE) { ++ dev_dbg(gasket_dev->dev, ++ "Interrupt system reported status %d.\n", status); ++ return status; ++ } ++ ++ for (i = 0; i < driver_desc->num_page_tables; ++i) { ++ status = gasket_page_table_system_status(gasket_dev->page_table[i]); ++ if (status != GASKET_STATUS_ALIVE) { ++ dev_dbg(gasket_dev->dev, ++ "Page table %d reported status %d.\n", ++ i, status); ++ return status; ++ } ++ } ++ ++ return GASKET_STATUS_ALIVE; ++} ++ ++static ssize_t ++gasket_write_mappable_regions(char *buf, ++ const struct gasket_driver_desc *driver_desc, ++ int bar_index) ++{ ++ int i; ++ ssize_t written; ++ ssize_t total_written = 0; ++ ulong min_addr, max_addr; ++ struct gasket_bar_desc bar_desc = ++ driver_desc->bar_descriptions[bar_index]; ++ ++ if (bar_desc.permissions == GASKET_NOMAP) ++ return 0; ++ for (i = 0; ++ i < bar_desc.num_mappable_regions && total_written < PAGE_SIZE; ++ i++) { ++ min_addr = bar_desc.mappable_regions[i].start - ++ driver_desc->legacy_mmap_address_offset; ++ max_addr = bar_desc.mappable_regions[i].start - ++ driver_desc->legacy_mmap_address_offset + ++ bar_desc.mappable_regions[i].length_bytes; ++ written = scnprintf(buf, PAGE_SIZE - total_written, ++ "0x%08lx-0x%08lx\n", min_addr, max_addr); ++ total_written += written; ++ buf += written; ++ } ++ return total_written; ++} ++ ++static ssize_t gasket_sysfs_data_show(struct device *device, ++ struct device_attribute *attr, char *buf) ++{ ++ int i, ret = 0; ++ ssize_t current_written = 0; ++ const struct gasket_driver_desc *driver_desc; ++ struct gasket_dev *gasket_dev; ++ struct gasket_sysfs_attribute *gasket_attr; ++ const struct gasket_bar_desc *bar_desc; ++ enum gasket_sysfs_attribute_type sysfs_type; ++ ++ gasket_dev = gasket_sysfs_get_device_data(device); ++ if (!gasket_dev) { ++ dev_err(device, "No sysfs mapping found for device\n"); ++ return 0; ++ } ++ ++ gasket_attr = gasket_sysfs_get_attr(device, attr); ++ if (!gasket_attr) { ++ dev_err(device, "No sysfs attr found for device\n"); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return 0; ++ } ++ ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ ++ sysfs_type = ++ (enum gasket_sysfs_attribute_type)gasket_attr->data.attr_type; ++ switch (sysfs_type) { ++ case ATTR_BAR_OFFSETS: ++ for (i = 0; i < GASKET_NUM_BARS; i++) { ++ bar_desc = &driver_desc->bar_descriptions[i]; ++ if (bar_desc->size == 0) ++ continue; ++ current_written = ++ snprintf(buf, PAGE_SIZE - ret, "%d: 0x%lx\n", i, ++ (ulong)bar_desc->base); ++ buf += current_written; ++ ret += current_written; ++ } ++ break; ++ case ATTR_BAR_SIZES: ++ for (i = 0; i < GASKET_NUM_BARS; i++) { ++ bar_desc = &driver_desc->bar_descriptions[i]; ++ if (bar_desc->size == 0) ++ continue; ++ current_written = ++ snprintf(buf, PAGE_SIZE - ret, "%d: 0x%lx\n", i, ++ (ulong)bar_desc->size); ++ buf += current_written; ++ ret += current_written; ++ } ++ break; ++ case ATTR_DRIVER_VERSION: ++ ret = snprintf(buf, PAGE_SIZE, "%s\n", ++ gasket_dev->internal_desc->driver_desc->driver_version); ++ break; ++ case ATTR_FRAMEWORK_VERSION: ++ ret = snprintf(buf, PAGE_SIZE, "%s\n", ++ GASKET_FRAMEWORK_VERSION); ++ break; ++ case ATTR_DEVICE_TYPE: ++ ret = snprintf(buf, PAGE_SIZE, "%s\n", ++ gasket_dev->internal_desc->driver_desc->name); ++ break; ++ case ATTR_HARDWARE_REVISION: ++ ret = snprintf(buf, PAGE_SIZE, "%d\n", ++ gasket_dev->hardware_revision); ++ break; ++ case ATTR_PCI_ADDRESS: ++ ret = snprintf(buf, PAGE_SIZE, "%s\n", gasket_dev->kobj_name); ++ break; ++ case ATTR_STATUS: ++ ret = snprintf(buf, PAGE_SIZE, "%s\n", ++ gasket_num_name_lookup(gasket_dev->status, ++ gasket_status_name_table)); ++ break; ++ case ATTR_IS_DEVICE_OWNED: ++ ret = snprintf(buf, PAGE_SIZE, "%d\n", ++ gasket_dev->dev_info.ownership.is_owned); ++ break; ++ case ATTR_DEVICE_OWNER: ++ ret = snprintf(buf, PAGE_SIZE, "%d\n", ++ gasket_dev->dev_info.ownership.owner); ++ break; ++ case ATTR_WRITE_OPEN_COUNT: ++ ret = snprintf(buf, PAGE_SIZE, "%d\n", ++ gasket_dev->dev_info.ownership.write_open_count); ++ break; ++ case ATTR_RESET_COUNT: ++ ret = snprintf(buf, PAGE_SIZE, "%d\n", gasket_dev->reset_count); ++ break; ++ case ATTR_USER_MEM_RANGES: ++ for (i = 0; i < GASKET_NUM_BARS; ++i) { ++ current_written = ++ gasket_write_mappable_regions(buf, driver_desc, ++ i); ++ buf += current_written; ++ ret += current_written; ++ } ++ break; ++ default: ++ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n", ++ attr->attr.name); ++ ret = 0; ++ break; ++ } ++ ++ gasket_sysfs_put_attr(device, gasket_attr); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return ret; ++} ++ ++/* These attributes apply to all Gasket driver instances. */ ++static const struct gasket_sysfs_attribute gasket_sysfs_generic_attrs[] = { ++ GASKET_SYSFS_RO(bar_offsets, gasket_sysfs_data_show, ATTR_BAR_OFFSETS), ++ GASKET_SYSFS_RO(bar_sizes, gasket_sysfs_data_show, ATTR_BAR_SIZES), ++ GASKET_SYSFS_RO(driver_version, gasket_sysfs_data_show, ++ ATTR_DRIVER_VERSION), ++ GASKET_SYSFS_RO(framework_version, gasket_sysfs_data_show, ++ ATTR_FRAMEWORK_VERSION), ++ GASKET_SYSFS_RO(device_type, gasket_sysfs_data_show, ATTR_DEVICE_TYPE), ++ GASKET_SYSFS_RO(revision, gasket_sysfs_data_show, ++ ATTR_HARDWARE_REVISION), ++ GASKET_SYSFS_RO(pci_address, gasket_sysfs_data_show, ATTR_PCI_ADDRESS), ++ GASKET_SYSFS_RO(status, gasket_sysfs_data_show, ATTR_STATUS), ++ GASKET_SYSFS_RO(is_device_owned, gasket_sysfs_data_show, ++ ATTR_IS_DEVICE_OWNED), ++ GASKET_SYSFS_RO(device_owner, gasket_sysfs_data_show, ++ ATTR_DEVICE_OWNER), ++ GASKET_SYSFS_RO(write_open_count, gasket_sysfs_data_show, ++ ATTR_WRITE_OPEN_COUNT), ++ GASKET_SYSFS_RO(reset_count, gasket_sysfs_data_show, ATTR_RESET_COUNT), ++ GASKET_SYSFS_RO(user_mem_ranges, gasket_sysfs_data_show, ++ ATTR_USER_MEM_RANGES), ++ GASKET_END_OF_ATTR_ARRAY ++}; ++ ++/* Add a char device and related info. */ ++static int gasket_add_cdev(struct gasket_cdev_info *dev_info, ++ const struct file_operations *file_ops, ++ struct module *owner) ++{ ++ int ret; ++ ++ cdev_init(&dev_info->cdev, file_ops); ++ dev_info->cdev.owner = owner; ++ ret = cdev_add(&dev_info->cdev, dev_info->devt, 1); ++ if (ret) { ++ dev_err(dev_info->gasket_dev_ptr->dev, ++ "cannot add char device [ret=%d]\n", ret); ++ return ret; ++ } ++ dev_info->cdev_added = 1; ++ ++ return 0; ++} ++ ++/* Disable device operations. */ ++void gasket_disable_device(struct gasket_dev *gasket_dev) ++{ ++ const struct gasket_driver_desc *driver_desc = ++ gasket_dev->internal_desc->driver_desc; ++ int i; ++ ++ dev_dbg(gasket_dev->dev, "disabling device\n"); ++ /* Only delete the device if it has been successfully added. */ ++ if (gasket_dev->dev_info.cdev_added) ++ cdev_del(&gasket_dev->dev_info.cdev); ++ ++ gasket_dev->status = GASKET_STATUS_DEAD; ++ ++ gasket_interrupt_cleanup(gasket_dev); ++ ++ for (i = 0; i < driver_desc->num_page_tables; ++i) { ++ if (gasket_dev->page_table[i]) { ++ gasket_page_table_reset(gasket_dev->page_table[i]); ++ gasket_page_table_cleanup(gasket_dev->page_table[i]); ++ } ++ } ++} ++EXPORT_SYMBOL(gasket_disable_device); ++ ++/* ++ * Registered driver descriptor lookup for PCI devices. ++ * ++ * Precondition: Called with g_mutex held (to avoid a race on return). ++ * Returns NULL if no matching device was found. ++ */ ++static struct gasket_internal_desc * ++lookup_pci_internal_desc(struct pci_dev *pci_dev) ++{ ++ int i; ++ ++ __must_hold(&g_mutex); ++ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { ++ if (g_descs[i].driver_desc && ++ g_descs[i].driver_desc->pci_id_table && ++ pci_match_id(g_descs[i].driver_desc->pci_id_table, pci_dev)) ++ return &g_descs[i]; ++ } ++ ++ return NULL; ++} ++ ++/* ++ * Registered driver descriptor lookup for platform devices. ++ * Caller must hold g_mutex. ++ */ ++static struct gasket_internal_desc * ++lookup_platform_internal_desc(struct platform_device *pdev) ++{ ++ int i; ++ ++ __must_hold(&g_mutex); ++ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { ++ if (g_descs[i].driver_desc && ++ strcmp(g_descs[i].driver_desc->name, pdev->name) == 0) ++ return &g_descs[i]; ++ } ++ ++ return NULL; ++} ++ ++/* ++ * Verifies that the user has permissions to perform the requested mapping and ++ * that the provided descriptor/range is of adequate size to hold the range to ++ * be mapped. ++ */ ++static bool gasket_mmap_has_permissions(struct gasket_dev *gasket_dev, ++ struct vm_area_struct *vma, ++ int bar_permissions) ++{ ++ int requested_permissions; ++ /* Always allow sysadmin to access. */ ++ if (capable(CAP_SYS_ADMIN)) ++ return true; ++ ++ /* Never allow non-sysadmins to access to a dead device. */ ++ if (gasket_dev->status != GASKET_STATUS_ALIVE) { ++ dev_dbg(gasket_dev->dev, "Device is dead.\n"); ++ return false; ++ } ++ ++ /* Make sure that no wrong flags are set. */ ++ requested_permissions = ++ (vma->vm_flags & (VM_WRITE | VM_READ | VM_EXEC)); ++ if (requested_permissions & ~(bar_permissions)) { ++ dev_dbg(gasket_dev->dev, ++ "Attempting to map a region with requested permissions " ++ "0x%x, but region has permissions 0x%x.\n", ++ requested_permissions, bar_permissions); ++ return false; ++ } ++ ++ /* Do not allow a non-owner to write. */ ++ if ((vma->vm_flags & VM_WRITE) && ++ !gasket_owned_by_current_tgid(&gasket_dev->dev_info)) { ++ dev_dbg(gasket_dev->dev, ++ "Attempting to mmap a region for write without owning " ++ "device.\n"); ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * Verifies that the input address is within the region allocated to coherent ++ * buffer. ++ */ ++static bool ++gasket_is_coherent_region(const struct gasket_driver_desc *driver_desc, ++ ulong address) ++{ ++ struct gasket_coherent_buffer_desc coh_buff_desc = ++ driver_desc->coherent_buffer_description; ++ ++ if (coh_buff_desc.permissions != GASKET_NOMAP) { ++ if ((address >= coh_buff_desc.base) && ++ (address < coh_buff_desc.base + coh_buff_desc.size)) { ++ return true; ++ } ++ } ++ return false; ++} ++ ++static int gasket_get_bar_index(const struct gasket_dev *gasket_dev, ++ ulong phys_addr) ++{ ++ int i; ++ const struct gasket_driver_desc *driver_desc; ++ ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ for (i = 0; i < GASKET_NUM_BARS; ++i) { ++ struct gasket_bar_desc bar_desc = ++ driver_desc->bar_descriptions[i]; ++ ++ if (bar_desc.permissions != GASKET_NOMAP) { ++ if (phys_addr >= bar_desc.base && ++ phys_addr < (bar_desc.base + bar_desc.size)) { ++ return i; ++ } ++ } ++ } ++ /* If we haven't found the address by now, it is invalid. */ ++ return -EINVAL; ++} ++ ++/* ++ * Sets the actual bounds to map, given the device's mappable region. ++ * ++ * Given the device's mappable region, along with the user-requested mapping ++ * start offset and length of the user region, determine how much of this ++ * mappable region can be mapped into the user's region (start/end offsets), ++ * and the physical offset (phys_offset) into the BAR where the mapping should ++ * begin (either the VMA's or region lower bound). ++ * ++ * In other words, this calculates the overlap between the VMA ++ * (bar_offset, requested_length) and the given gasket_mappable_region. ++ * ++ * Returns true if there's anything to map, and false otherwise. ++ */ ++static bool ++gasket_mm_get_mapping_addrs(const struct gasket_mappable_region *region, ++ ulong bar_offset, ulong requested_length, ++ struct gasket_mappable_region *mappable_region, ++ ulong *virt_offset) ++{ ++ ulong range_start = region->start; ++ ulong range_length = region->length_bytes; ++ ulong range_end = range_start + range_length; ++ ++ *virt_offset = 0; ++ if (bar_offset + requested_length < range_start) { ++ /* ++ * If the requested region is completely below the range, ++ * there is nothing to map. ++ */ ++ return false; ++ } else if (bar_offset <= range_start) { ++ /* If the bar offset is below this range's start ++ * but the requested length continues into it: ++ * 1) Only map starting from the beginning of this ++ * range's phys. offset, so we don't map unmappable ++ * memory. ++ * 2) The length of the virtual memory to not map is the ++ * delta between the bar offset and the ++ * mappable start (and since the mappable start is ++ * bigger, start - req.) ++ * 3) The map length is the minimum of the mappable ++ * requested length (requested_length - virt_offset) ++ * and the actual mappable length of the range. ++ */ ++ mappable_region->start = range_start; ++ *virt_offset = range_start - bar_offset; ++ mappable_region->length_bytes = ++ min(requested_length - *virt_offset, range_length); ++ return true; ++ } else if (bar_offset > range_start && ++ bar_offset < range_end) { ++ /* ++ * If the bar offset is within this range: ++ * 1) Map starting from the bar offset. ++ * 2) Because there is no forbidden memory between the ++ * bar offset and the range start, ++ * virt_offset is 0. ++ * 3) The map length is the minimum of the requested ++ * length and the remaining length in the buffer ++ * (range_end - bar_offset) ++ */ ++ mappable_region->start = bar_offset; ++ *virt_offset = 0; ++ mappable_region->length_bytes = ++ min(requested_length, range_end - bar_offset); ++ return true; ++ } ++ ++ /* ++ * If the requested [start] offset is above range_end, ++ * there's nothing to map. ++ */ ++ return false; ++} ++ ++/* ++ * Calculates the offset where the VMA range begins in its containing BAR. ++ * The offset is written into bar_offset on success. ++ * Returns zero on success, anything else on error. ++ */ ++static int gasket_mm_vma_bar_offset(const struct gasket_dev *gasket_dev, ++ const struct vm_area_struct *vma, ++ ulong *bar_offset) ++{ ++ ulong raw_offset; ++ int bar_index; ++ const struct gasket_driver_desc *driver_desc = ++ gasket_dev->internal_desc->driver_desc; ++ ++ raw_offset = (vma->vm_pgoff << PAGE_SHIFT) + ++ driver_desc->legacy_mmap_address_offset; ++ bar_index = gasket_get_bar_index(gasket_dev, raw_offset); ++ if (bar_index < 0) { ++ dev_err(gasket_dev->dev, ++ "Unable to find matching bar for address 0x%lx\n", ++ raw_offset); ++ trace_gasket_mmap_exit(bar_index); ++ return bar_index; ++ } ++ *bar_offset = ++ raw_offset - driver_desc->bar_descriptions[bar_index].base; ++ ++ return 0; ++} ++ ++int gasket_mm_unmap_region(const struct gasket_dev *gasket_dev, ++ struct vm_area_struct *vma, ++ const struct gasket_mappable_region *map_region) ++{ ++ ulong bar_offset; ++ ulong virt_offset; ++ struct gasket_mappable_region mappable_region; ++ int ret; ++ ++ if (map_region->length_bytes == 0) ++ return 0; ++ ++ ret = gasket_mm_vma_bar_offset(gasket_dev, vma, &bar_offset); ++ if (ret) ++ return ret; ++ ++ if (!gasket_mm_get_mapping_addrs(map_region, bar_offset, ++ vma->vm_end - vma->vm_start, ++ &mappable_region, &virt_offset)) ++ return 1; ++ ++ /* ++ * The length passed to zap_vma_ptes MUST BE A MULTIPLE OF ++ * PAGE_SIZE! Trust me. I have the scars. ++ * ++ * Next multiple of y: ceil_div(x, y) * y ++ */ ++ zap_vma_ptes(vma, vma->vm_start + virt_offset, ++ DIV_ROUND_UP(mappable_region.length_bytes, PAGE_SIZE) * ++ PAGE_SIZE); ++ return 0; ++} ++EXPORT_SYMBOL(gasket_mm_unmap_region); ++ ++/* Maps a virtual address + range to a physical offset of a BAR. */ ++static enum do_map_region_status ++do_map_region(const struct gasket_dev *gasket_dev, struct vm_area_struct *vma, ++ struct gasket_mappable_region *mappable_region) ++{ ++ /* Maximum size of a single call to io_remap_pfn_range. */ ++ /* I pulled this number out of thin air. */ ++ const ulong max_chunk_size = 64 * 1024 * 1024; ++ ulong chunk_size, mapped_bytes = 0; ++ ++ const struct gasket_driver_desc *driver_desc = ++ gasket_dev->internal_desc->driver_desc; ++ ++ ulong bar_offset, virt_offset; ++ struct gasket_mappable_region region_to_map; ++ ulong phys_offset, map_length; ++ ulong virt_base, phys_base; ++ int bar_index, ret; ++ ++ ret = gasket_mm_vma_bar_offset(gasket_dev, vma, &bar_offset); ++ if (ret) ++ return DO_MAP_REGION_INVALID; ++ ++ if (!gasket_mm_get_mapping_addrs(mappable_region, bar_offset, ++ vma->vm_end - vma->vm_start, ++ ®ion_to_map, &virt_offset)) ++ return DO_MAP_REGION_INVALID; ++ phys_offset = region_to_map.start; ++ map_length = region_to_map.length_bytes; ++ ++ virt_base = vma->vm_start + virt_offset; ++ bar_index = ++ gasket_get_bar_index(gasket_dev, ++ (vma->vm_pgoff << PAGE_SHIFT) + ++ driver_desc->legacy_mmap_address_offset); ++ phys_base = gasket_dev->bar_data[bar_index].phys_base + phys_offset; ++ while (mapped_bytes < map_length) { ++ /* ++ * io_remap_pfn_range can take a while, so we chunk its ++ * calls and call cond_resched between each. ++ */ ++ chunk_size = min(max_chunk_size, map_length - mapped_bytes); ++ ++ cond_resched(); ++ ret = io_remap_pfn_range(vma, virt_base + mapped_bytes, ++ (phys_base + mapped_bytes) >> ++ PAGE_SHIFT, chunk_size, ++ vma->vm_page_prot); ++ if (ret) { ++ dev_err(gasket_dev->dev, ++ "Error remapping PFN range.\n"); ++ goto fail; ++ } ++ mapped_bytes += chunk_size; ++ } ++ ++ return DO_MAP_REGION_SUCCESS; ++ ++fail: ++ /* Unmap the partial chunk we mapped. */ ++ mappable_region->length_bytes = mapped_bytes; ++ if (gasket_mm_unmap_region(gasket_dev, vma, mappable_region)) ++ dev_err(gasket_dev->dev, ++ "Error unmapping partial region 0x%lx (0x%lx bytes)\n", ++ (ulong)virt_offset, ++ (ulong)mapped_bytes); ++ ++ return DO_MAP_REGION_FAILURE; ++} ++ ++/* Map a region of coherent memory. */ ++static int gasket_mmap_coherent(struct gasket_dev *gasket_dev, ++ struct vm_area_struct *vma) ++{ ++ const struct gasket_driver_desc *driver_desc = ++ gasket_dev->internal_desc->driver_desc; ++ const ulong requested_length = vma->vm_end - vma->vm_start; ++ int ret; ++ ulong permissions; ++ ++ if (requested_length == 0 || requested_length > ++ gasket_dev->coherent_buffer.length_bytes) { ++ trace_gasket_mmap_exit(-EINVAL); ++ return -EINVAL; ++ } ++ ++ permissions = driver_desc->coherent_buffer_description.permissions; ++ if (!gasket_mmap_has_permissions(gasket_dev, vma, permissions)) { ++ dev_err(gasket_dev->dev, "Permission checking failed.\n"); ++ trace_gasket_mmap_exit(-EPERM); ++ return -EPERM; ++ } ++ ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ vma->vm_pgoff = 0; ++ ret = dma_mmap_coherent(gasket_dev->dma_dev, vma, ++ gasket_dev->coherent_buffer.virt_base, ++ gasket_dev->coherent_buffer.phys_base, ++ requested_length); ++ if (ret) { ++ dev_err(gasket_dev->dev, ++ "Error mmapping coherent buffer err=%d.\n", ret); ++ trace_gasket_mmap_exit(ret); ++ return ret; ++ } ++ ++ /* Record the user virtual to dma_address mapping that was ++ * created by the kernel. ++ */ ++ gasket_set_user_virt(gasket_dev, requested_length, ++ gasket_dev->coherent_buffer.phys_base, ++ vma->vm_start); ++ return 0; ++} ++ ++/* Map a device's BARs into user space. */ ++static int gasket_mmap(struct file *filp, struct vm_area_struct *vma) ++{ ++ int i, ret; ++ int bar_index; ++ int has_mapped_anything = 0; ++ ulong permissions; ++ ulong raw_offset, vma_size; ++ bool is_coherent_region; ++ const struct gasket_driver_desc *driver_desc; ++ struct gasket_dev *gasket_dev = (struct gasket_dev *)filp->private_data; ++ const struct gasket_bar_desc *bar_desc; ++ struct gasket_mappable_region *map_regions = NULL; ++ int num_map_regions = 0; ++ enum do_map_region_status map_status; ++ ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ ++ if (vma->vm_start & ~PAGE_MASK) { ++ dev_err(gasket_dev->dev, ++ "Base address not page-aligned: 0x%lx\n", ++ vma->vm_start); ++ trace_gasket_mmap_exit(-EINVAL); ++ return -EINVAL; ++ } ++ ++ /* Calculate the offset of this range into physical mem. */ ++ raw_offset = (vma->vm_pgoff << PAGE_SHIFT) + ++ driver_desc->legacy_mmap_address_offset; ++ vma_size = vma->vm_end - vma->vm_start; ++ trace_gasket_mmap_entry(gasket_dev->dev_info.name, raw_offset, ++ vma_size); ++ ++ /* ++ * Check if the raw offset is within a bar region. If not, check if it ++ * is a coherent region. ++ */ ++ bar_index = gasket_get_bar_index(gasket_dev, raw_offset); ++ is_coherent_region = gasket_is_coherent_region(driver_desc, raw_offset); ++ if (bar_index < 0 && !is_coherent_region) { ++ dev_err(gasket_dev->dev, ++ "Unable to find matching bar for address 0x%lx\n", ++ raw_offset); ++ trace_gasket_mmap_exit(bar_index); ++ return bar_index; ++ } ++ if (bar_index > 0 && is_coherent_region) { ++ dev_err(gasket_dev->dev, ++ "double matching bar and coherent buffers for address " ++ "0x%lx\n", ++ raw_offset); ++ trace_gasket_mmap_exit(bar_index); ++ return -EINVAL; ++ } ++ ++ vma->vm_private_data = gasket_dev; ++ ++ if (is_coherent_region) ++ return gasket_mmap_coherent(gasket_dev, vma); ++ ++ /* Everything in the rest of this function is for normal BAR mapping. */ ++ ++ /* ++ * Subtract the base of the bar from the raw offset to get the ++ * memory location within the bar to map. ++ */ ++ bar_desc = &driver_desc->bar_descriptions[bar_index]; ++ permissions = bar_desc->permissions; ++ if (!gasket_mmap_has_permissions(gasket_dev, vma, permissions)) { ++ dev_err(gasket_dev->dev, "Permission checking failed.\n"); ++ trace_gasket_mmap_exit(-EPERM); ++ return -EPERM; ++ } ++ ++ if (driver_desc->get_mappable_regions_cb) { ++ ret = driver_desc->get_mappable_regions_cb(gasket_dev, ++ bar_index, ++ &map_regions, ++ &num_map_regions); ++ if (ret) ++ return ret; ++ } else { ++ if (!gasket_mmap_has_permissions(gasket_dev, vma, ++ bar_desc->permissions)) { ++ dev_err(gasket_dev->dev, ++ "Permission checking failed.\n"); ++ trace_gasket_mmap_exit(-EPERM); ++ return -EPERM; ++ } ++ num_map_regions = bar_desc->num_mappable_regions; ++ map_regions = kcalloc(num_map_regions, ++ sizeof(*bar_desc->mappable_regions), ++ GFP_KERNEL); ++ if (map_regions) { ++ memcpy(map_regions, bar_desc->mappable_regions, ++ num_map_regions * ++ sizeof(*bar_desc->mappable_regions)); ++ } ++ } ++ ++ if (!map_regions || num_map_regions == 0) { ++ dev_err(gasket_dev->dev, "No mappable regions returned!\n"); ++ return -EINVAL; ++ } ++ ++ /* Marks the VMA's pages as uncacheable. */ ++ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); ++ for (i = 0; i < num_map_regions; i++) { ++ map_status = do_map_region(gasket_dev, vma, &map_regions[i]); ++ /* Try the next region if this one was not mappable. */ ++ if (map_status == DO_MAP_REGION_INVALID) ++ continue; ++ if (map_status == DO_MAP_REGION_FAILURE) { ++ ret = -ENOMEM; ++ goto fail; ++ } ++ ++ has_mapped_anything = 1; ++ } ++ ++ kfree(map_regions); ++ ++ /* If we could not map any memory, the request was invalid. */ ++ if (!has_mapped_anything) { ++ dev_err(gasket_dev->dev, ++ "Map request did not contain a valid region.\n"); ++ trace_gasket_mmap_exit(-EINVAL); ++ return -EINVAL; ++ } ++ ++ trace_gasket_mmap_exit(0); ++ return 0; ++ ++fail: ++ /* Need to unmap any mapped ranges. */ ++ num_map_regions = i; ++ for (i = 0; i < num_map_regions; i++) ++ if (gasket_mm_unmap_region(gasket_dev, vma, ++ &bar_desc->mappable_regions[i])) ++ dev_err(gasket_dev->dev, "Error unmapping range %d.\n", ++ i); ++ kfree(map_regions); ++ ++ return ret; ++} ++ ++/* ++ * Open the char device file. ++ * ++ * If the open is for writing, and the device is not owned, this process becomes ++ * the owner. If the open is for writing and the device is already owned by ++ * some other process, it is an error. If this process is the owner, increment ++ * the open count. ++ * ++ * Returns 0 if successful, a negative error number otherwise. ++ */ ++static int gasket_open(struct inode *inode, struct file *filp) ++{ ++ int ret; ++ struct gasket_dev *gasket_dev; ++ const struct gasket_driver_desc *driver_desc; ++ struct gasket_ownership *ownership; ++ char task_name[TASK_COMM_LEN]; ++ struct gasket_cdev_info *dev_info = ++ container_of(inode->i_cdev, struct gasket_cdev_info, cdev); ++ struct pid_namespace *pid_ns = task_active_pid_ns(current); ++ bool is_root = ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN); ++ ++ gasket_dev = dev_info->gasket_dev_ptr; ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ ownership = &dev_info->ownership; ++ get_task_comm(task_name, current); ++ filp->private_data = gasket_dev; ++ inode->i_size = 0; ++ ++ dev_dbg(gasket_dev->dev, ++ "Attempting to open with tgid %u (%s) (f_mode: 0%03o, " ++ "fmode_write: %d is_root: %u)\n", ++ current->tgid, task_name, filp->f_mode, ++ (filp->f_mode & FMODE_WRITE), is_root); ++ ++ /* Always allow non-writing accesses. */ ++ if (!(filp->f_mode & FMODE_WRITE)) { ++ dev_dbg(gasket_dev->dev, "Allowing read-only opening.\n"); ++ return 0; ++ } ++ ++ mutex_lock(&gasket_dev->mutex); ++ ++ dev_dbg(gasket_dev->dev, ++ "Current owner open count (owning tgid %u): %d.\n", ++ ownership->owner, ownership->write_open_count); ++ ++ /* Opening a node owned by another TGID is an error (unless root) */ ++ if (ownership->is_owned && ownership->owner != current->tgid && ++ !is_root) { ++ dev_err(gasket_dev->dev, ++ "Process %u is opening a node held by %u.\n", ++ current->tgid, ownership->owner); ++ mutex_unlock(&gasket_dev->mutex); ++ return -EPERM; ++ } ++ ++ /* If the node is not owned, assign it to the current TGID. */ ++ if (!ownership->is_owned) { ++ ret = gasket_check_and_invoke_callback_nolock(gasket_dev, ++ driver_desc->device_open_cb); ++ if (ret) { ++ dev_err(gasket_dev->dev, ++ "Error in device open cb: %d\n", ret); ++ mutex_unlock(&gasket_dev->mutex); ++ return ret; ++ } ++ ownership->is_owned = 1; ++ ownership->owner = current->tgid; ++ dev_dbg(gasket_dev->dev, "Device owner is now tgid %u\n", ++ ownership->owner); ++ } ++ ++ ownership->write_open_count++; ++ ++ dev_dbg(gasket_dev->dev, "New open count (owning tgid %u): %d\n", ++ ownership->owner, ownership->write_open_count); ++ ++ mutex_unlock(&gasket_dev->mutex); ++ return 0; ++} ++ ++/* ++ * Called on a close of the device file. If this process is the owner, ++ * decrement the open count. On last close by the owner, free up buffers and ++ * eventfd contexts, and release ownership. ++ * ++ * Returns 0 if successful, a negative error number otherwise. ++ */ ++static int gasket_release(struct inode *inode, struct file *file) ++{ ++ int i; ++ struct gasket_dev *gasket_dev; ++ struct gasket_ownership *ownership; ++ const struct gasket_driver_desc *driver_desc; ++ char task_name[TASK_COMM_LEN]; ++ struct gasket_cdev_info *dev_info = ++ container_of(inode->i_cdev, struct gasket_cdev_info, cdev); ++ struct pid_namespace *pid_ns = task_active_pid_ns(current); ++ bool is_root = ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN); ++ ++ gasket_dev = dev_info->gasket_dev_ptr; ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ ownership = &dev_info->ownership; ++ get_task_comm(task_name, current); ++ mutex_lock(&gasket_dev->mutex); ++ ++ dev_dbg(gasket_dev->dev, ++ "Releasing device node. Call origin: tgid %u (%s) " ++ "(f_mode: 0%03o, fmode_write: %d, is_root: %u)\n", ++ current->tgid, task_name, file->f_mode, ++ (file->f_mode & FMODE_WRITE), is_root); ++ dev_dbg(gasket_dev->dev, "Current open count (owning tgid %u): %d\n", ++ ownership->owner, ownership->write_open_count); ++ ++ if (file->f_mode & FMODE_WRITE) { ++ ownership->write_open_count--; ++ if (ownership->write_open_count == 0) { ++ dev_dbg(gasket_dev->dev, "Device is now free\n"); ++ ownership->is_owned = 0; ++ ownership->owner = 0; ++ ++ /* Forces chip reset before we unmap the page tables. */ ++ driver_desc->device_reset_cb(gasket_dev); ++ ++ for (i = 0; i < driver_desc->num_page_tables; ++i) { ++ gasket_page_table_unmap_all(gasket_dev->page_table[i]); ++ gasket_page_table_garbage_collect(gasket_dev->page_table[i]); ++ gasket_free_coherent_memory_all(gasket_dev, i); ++ } ++ ++ /* Closes device, enters power save. */ ++ gasket_check_and_invoke_callback_nolock(gasket_dev, ++ driver_desc->device_close_cb); ++ } ++ } ++ ++ dev_dbg(gasket_dev->dev, "New open count (owning tgid %u): %d\n", ++ ownership->owner, ownership->write_open_count); ++ mutex_unlock(&gasket_dev->mutex); ++ return 0; ++} ++ ++/* ++ * Gasket ioctl dispatch function. ++ * ++ * Check if the ioctl is a generic ioctl. If not, pass the ioctl to the ++ * ioctl_handler_cb registered in the driver description. ++ * If the ioctl is a generic ioctl, pass it to gasket_ioctl_handler. ++ */ ++static long gasket_ioctl(struct file *filp, uint cmd, ulong arg) ++{ ++ struct gasket_dev *gasket_dev; ++ const struct gasket_driver_desc *driver_desc; ++ void __user *argp = (void __user *)arg; ++ char path[256]; ++ ++ gasket_dev = (struct gasket_dev *)filp->private_data; ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ if (!driver_desc) { ++ dev_dbg(gasket_dev->dev, ++ "Unable to find device descriptor for file %s\n", ++ d_path(&filp->f_path, path, 256)); ++ return -ENODEV; ++ } ++ ++ if (!gasket_is_supported_ioctl(cmd)) { ++ /* ++ * The ioctl handler is not a standard Gasket callback, since ++ * it requires different arguments. This means we can't use ++ * check_and_invoke_callback. ++ */ ++ if (driver_desc->ioctl_handler_cb) ++ return driver_desc->ioctl_handler_cb(filp, cmd, argp); ++ ++ dev_dbg(gasket_dev->dev, "Received unknown ioctl 0x%x\n", cmd); ++ return -EINVAL; ++ } ++ ++ return gasket_handle_ioctl(filp, cmd, argp); ++} ++ ++/* File operations for all Gasket devices. */ ++static const struct file_operations gasket_file_ops = { ++ .owner = THIS_MODULE, ++ .llseek = no_llseek, ++ .mmap = gasket_mmap, ++ .open = gasket_open, ++ .release = gasket_release, ++ .unlocked_ioctl = gasket_ioctl, ++ .compat_ioctl = gasket_ioctl, ++}; ++ ++/* Perform final init and marks the device as active. */ ++int gasket_enable_device(struct gasket_dev *gasket_dev) ++{ ++ int tbl_idx; ++ int ret; ++ const struct gasket_driver_desc *driver_desc = ++ gasket_dev->internal_desc->driver_desc; ++ ++ dev_dbg(gasket_dev->dev, "enabling device\n"); ++ ret = gasket_interrupt_init(gasket_dev); ++ if (ret) { ++ dev_err(gasket_dev->dev, ++ "Critical failure to allocate interrupts: %d\n", ret); ++ gasket_interrupt_cleanup(gasket_dev); ++ return ret; ++ } ++ ++ for (tbl_idx = 0; tbl_idx < driver_desc->num_page_tables; tbl_idx++) { ++ dev_dbg(gasket_dev->dev, "Initializing page table %d.\n", ++ tbl_idx); ++ ret = gasket_page_table_init(&gasket_dev->page_table[tbl_idx], ++ &gasket_dev->bar_data[driver_desc->page_table_bar_index], ++ &driver_desc->page_table_configs[tbl_idx], ++ gasket_dev->dev, ++ gasket_dev->pci_dev); ++ if (ret) { ++ dev_err(gasket_dev->dev, ++ "Couldn't init page table %d: %d\n", ++ tbl_idx, ret); ++ return ret; ++ } ++ /* ++ * Make sure that the page table is clear and set to simple ++ * addresses. ++ */ ++ gasket_page_table_reset(gasket_dev->page_table[tbl_idx]); ++ } ++ ++ /* ++ * hardware_revision_cb returns a positive integer (the rev) if ++ * successful.) ++ */ ++ ret = check_and_invoke_callback(gasket_dev, ++ driver_desc->hardware_revision_cb); ++ if (ret < 0) { ++ dev_err(gasket_dev->dev, ++ "Error getting hardware revision: %d\n", ret); ++ return ret; ++ } ++ gasket_dev->hardware_revision = ret; ++ ++ /* device_status_cb returns a device status, not an error code. */ ++ gasket_dev->status = gasket_get_hw_status(gasket_dev); ++ if (gasket_dev->status == GASKET_STATUS_DEAD) ++ dev_err(gasket_dev->dev, "Device reported as unhealthy.\n"); ++ ++ ret = gasket_add_cdev(&gasket_dev->dev_info, &gasket_file_ops, ++ driver_desc->module); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++EXPORT_SYMBOL(gasket_enable_device); ++ ++static int __gasket_add_device(struct device *parent_dev, ++ struct gasket_internal_desc *internal_desc, ++ struct gasket_dev **gasket_devp) ++{ ++ int ret; ++ struct gasket_dev *gasket_dev; ++ const struct gasket_driver_desc *driver_desc = ++ internal_desc->driver_desc; ++ ++ ret = gasket_alloc_dev(internal_desc, parent_dev, &gasket_dev); ++ if (ret) ++ return ret; ++ if (IS_ERR(gasket_dev->dev_info.device)) { ++ dev_err(parent_dev, "Cannot create %s device %s [ret = %ld]\n", ++ driver_desc->name, gasket_dev->dev_info.name, ++ PTR_ERR(gasket_dev->dev_info.device)); ++ ret = -ENODEV; ++ goto free_gasket_dev; ++ } ++ ++ ret = gasket_sysfs_create_mapping(gasket_dev->dev_info.device, ++ gasket_dev); ++ if (ret) ++ goto remove_device; ++ ++ ret = gasket_sysfs_create_entries(gasket_dev->dev_info.device, ++ gasket_sysfs_generic_attrs); ++ if (ret) ++ goto remove_sysfs_mapping; ++ ++ *gasket_devp = gasket_dev; ++ return 0; ++ ++remove_sysfs_mapping: ++ gasket_sysfs_remove_mapping(gasket_dev->dev_info.device); ++remove_device: ++ device_destroy(internal_desc->class, gasket_dev->dev_info.devt); ++free_gasket_dev: ++ gasket_free_dev(gasket_dev); ++ return ret; ++} ++ ++static void __gasket_remove_device(struct gasket_internal_desc *internal_desc, ++ struct gasket_dev *gasket_dev) ++{ ++ gasket_sysfs_remove_mapping(gasket_dev->dev_info.device); ++ device_destroy(internal_desc->class, gasket_dev->dev_info.devt); ++ gasket_free_dev(gasket_dev); ++} ++ ++/* ++ * Add PCI gasket device. ++ * ++ * Called by Gasket device probe function. ++ * Allocates device metadata and maps device memory. The device driver must ++ * call gasket_enable_device after driver init is complete to place the device ++ * in active use. ++ */ ++int gasket_pci_add_device(struct pci_dev *pci_dev, ++ struct gasket_dev **gasket_devp) ++{ ++ int ret; ++ struct gasket_internal_desc *internal_desc; ++ struct gasket_dev *gasket_dev; ++ struct device *parent; ++ ++ dev_dbg(&pci_dev->dev, "add PCI gasket device\n"); ++ ++ mutex_lock(&g_mutex); ++ internal_desc = lookup_pci_internal_desc(pci_dev); ++ mutex_unlock(&g_mutex); ++ if (!internal_desc) { ++ dev_err(&pci_dev->dev, ++ "PCI add device called for unknown driver type\n"); ++ return -ENODEV; ++ } ++ ++ parent = &pci_dev->dev; ++ ret = __gasket_add_device(parent, internal_desc, &gasket_dev); ++ if (ret) ++ return ret; ++ ++ gasket_dev->pci_dev = pci_dev; ++ ret = gasket_setup_pci(pci_dev, gasket_dev); ++ if (ret) ++ goto cleanup_pci; ++ ++ /* ++ * Once we've created the mapping structures successfully, attempt to ++ * create a symlink to the pci directory of this object. ++ */ ++ ret = sysfs_create_link(&gasket_dev->dev_info.device->kobj, ++ &pci_dev->dev.kobj, dev_name(&pci_dev->dev)); ++ if (ret) { ++ dev_err(gasket_dev->dev, ++ "Cannot create sysfs pci link: %d\n", ret); ++ goto cleanup_pci; ++ } ++ ++ *gasket_devp = gasket_dev; ++ return 0; ++ ++cleanup_pci: ++ gasket_cleanup_pci(gasket_dev); ++ __gasket_remove_device(internal_desc, gasket_dev); ++ return ret; ++} ++EXPORT_SYMBOL(gasket_pci_add_device); ++ ++/* Remove a PCI gasket device. */ ++void gasket_pci_remove_device(struct pci_dev *pci_dev) ++{ ++ int i; ++ struct gasket_internal_desc *internal_desc; ++ struct gasket_dev *gasket_dev = NULL; ++ /* Find the device desc. */ ++ mutex_lock(&g_mutex); ++ internal_desc = lookup_pci_internal_desc(pci_dev); ++ if (!internal_desc) { ++ mutex_unlock(&g_mutex); ++ return; ++ } ++ mutex_unlock(&g_mutex); ++ ++ /* Now find the specific device */ ++ mutex_lock(&internal_desc->mutex); ++ for (i = 0; i < GASKET_DEV_MAX; i++) { ++ if (internal_desc->devs[i] && ++ internal_desc->devs[i]->pci_dev == pci_dev) { ++ gasket_dev = internal_desc->devs[i]; ++ break; ++ } ++ } ++ mutex_unlock(&internal_desc->mutex); ++ ++ if (!gasket_dev) ++ return; ++ ++ dev_dbg(gasket_dev->dev, "remove %s PCI gasket device\n", ++ internal_desc->driver_desc->name); ++ ++ gasket_cleanup_pci(gasket_dev); ++ __gasket_remove_device(internal_desc, gasket_dev); ++} ++EXPORT_SYMBOL(gasket_pci_remove_device); ++ ++/* Add platform gasket device. Called by Gasket device probe function. */ ++int gasket_platform_add_device(struct platform_device *pdev, ++ struct gasket_dev **gasket_devp) ++{ ++ int ret; ++ struct gasket_internal_desc *internal_desc; ++ struct gasket_dev *gasket_dev; ++ struct device *parent; ++ ++ dev_dbg(&pdev->dev, "add platform gasket device\n"); ++ ++ mutex_lock(&g_mutex); ++ internal_desc = lookup_platform_internal_desc(pdev); ++ mutex_unlock(&g_mutex); ++ if (!internal_desc) { ++ dev_err(&pdev->dev, ++ "%s called for unknown driver type\n", __func__); ++ return -ENODEV; ++ } ++ ++ parent = &pdev->dev; ++ ret = __gasket_add_device(parent, internal_desc, &gasket_dev); ++ if (ret) ++ return ret; ++ ++ gasket_dev->platform_dev = pdev; ++ *gasket_devp = gasket_dev; ++ return 0; ++} ++EXPORT_SYMBOL(gasket_platform_add_device); ++ ++/* Remove a platform gasket device. */ ++void gasket_platform_remove_device(struct platform_device *pdev) ++{ ++ int i; ++ struct gasket_internal_desc *internal_desc; ++ struct gasket_dev *gasket_dev = NULL; ++ ++ /* Find the device desc. */ ++ mutex_lock(&g_mutex); ++ internal_desc = lookup_platform_internal_desc(pdev); ++ mutex_unlock(&g_mutex); ++ if (!internal_desc) ++ return; ++ ++ /* Now find the specific device */ ++ mutex_lock(&internal_desc->mutex); ++ for (i = 0; i < GASKET_DEV_MAX; i++) { ++ if (internal_desc->devs[i] && ++ internal_desc->devs[i]->platform_dev == pdev) { ++ gasket_dev = internal_desc->devs[i]; ++ break; ++ } ++ } ++ mutex_unlock(&internal_desc->mutex); ++ ++ if (!gasket_dev) ++ return; ++ ++ dev_dbg(gasket_dev->dev, "remove %s platform gasket device\n", ++ internal_desc->driver_desc->name); ++ ++ __gasket_remove_device(internal_desc, gasket_dev); ++} ++EXPORT_SYMBOL(gasket_platform_remove_device); ++ ++void gasket_set_dma_device(struct gasket_dev *gasket_dev, ++ struct device *dma_dev) ++{ ++ put_device(gasket_dev->dma_dev); ++ gasket_dev->dma_dev = get_device(dma_dev); ++} ++EXPORT_SYMBOL(gasket_set_dma_device); ++ ++/** ++ * Lookup a name by number in a num_name table. ++ * @num: Number to lookup. ++ * @table: Array of num_name structures, the table for the lookup. ++ * ++ * Description: Searches for num in the table. If found, the ++ * corresponding name is returned; otherwise NULL ++ * is returned. ++ * ++ * The table must have a NULL name pointer at the end. ++ */ ++const char *gasket_num_name_lookup(uint num, ++ const struct gasket_num_name *table) ++{ ++ uint i = 0; ++ ++ while (table[i].snn_name) { ++ if (num == table[i].snn_num) ++ break; ++ ++i; ++ } ++ ++ return table[i].snn_name; ++} ++EXPORT_SYMBOL(gasket_num_name_lookup); ++ ++int gasket_reset(struct gasket_dev *gasket_dev) ++{ ++ int ret; ++ ++ mutex_lock(&gasket_dev->mutex); ++ ret = gasket_reset_nolock(gasket_dev); ++ mutex_unlock(&gasket_dev->mutex); ++ return ret; ++} ++EXPORT_SYMBOL(gasket_reset); ++ ++int gasket_reset_nolock(struct gasket_dev *gasket_dev) ++{ ++ int ret; ++ int i; ++ const struct gasket_driver_desc *driver_desc; ++ ++ driver_desc = gasket_dev->internal_desc->driver_desc; ++ if (!driver_desc->device_reset_cb) ++ return 0; ++ ++ ret = driver_desc->device_reset_cb(gasket_dev); ++ if (ret) { ++ dev_dbg(gasket_dev->dev, "Device reset cb returned %d.\n", ++ ret); ++ return ret; ++ } ++ ++ /* Reinitialize the page tables and interrupt framework. */ ++ for (i = 0; i < driver_desc->num_page_tables; ++i) ++ gasket_page_table_reset(gasket_dev->page_table[i]); ++ ++ ret = gasket_interrupt_reinit(gasket_dev); ++ if (ret) { ++ dev_dbg(gasket_dev->dev, "Unable to reinit interrupts: %d.\n", ++ ret); ++ return ret; ++ } ++ ++ /* Get current device health. */ ++ gasket_dev->status = gasket_get_hw_status(gasket_dev); ++ if (gasket_dev->status == GASKET_STATUS_DEAD) { ++ dev_dbg(gasket_dev->dev, "Device reported as dead.\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL(gasket_reset_nolock); ++ ++gasket_ioctl_permissions_cb_t ++gasket_get_ioctl_permissions_cb(struct gasket_dev *gasket_dev) ++{ ++ return gasket_dev->internal_desc->driver_desc->ioctl_permissions_cb; ++} ++EXPORT_SYMBOL(gasket_get_ioctl_permissions_cb); ++ ++/* Get the driver structure for a given gasket_dev. ++ * @dev: pointer to gasket_dev, implementing the requested driver. ++ */ ++const struct gasket_driver_desc *gasket_get_driver_desc(struct gasket_dev *dev) ++{ ++ return dev->internal_desc->driver_desc; ++} ++ ++/* Get the device structure for a given gasket_dev. ++ * @dev: pointer to gasket_dev, implementing the requested driver. ++ */ ++struct device *gasket_get_device(struct gasket_dev *dev) ++{ ++ return dev->dev; ++} ++ ++/** ++ * Asynchronously waits on device. ++ * @gasket_dev: Device struct. ++ * @bar: Bar ++ * @offset: Register offset ++ * @mask: Register mask ++ * @val: Expected value ++ * @max_retries: number of sleep periods ++ * @delay_ms: Timeout in milliseconds ++ * ++ * Description: Busy waits for a specific combination of bits to be set on a ++ * Gasket register. ++ **/ ++int gasket_wait_with_reschedule(struct gasket_dev *gasket_dev, int bar, ++ u64 offset, u64 mask, u64 val, ++ uint max_retries, u64 delay_ms) ++{ ++ uint retries = 0; ++ u64 tmp; ++ ++ while (retries < max_retries) { ++ tmp = gasket_dev_read_64(gasket_dev, bar, offset); ++ if ((tmp & mask) == val) ++ return 0; ++ msleep(delay_ms); ++ retries++; ++ } ++ dev_dbg(gasket_dev->dev, "%s timeout: reg %llx timeout (%llu ms)\n", ++ __func__, offset, max_retries * delay_ms); ++ return -ETIMEDOUT; ++} ++EXPORT_SYMBOL(gasket_wait_with_reschedule); ++ ++/* See gasket_core.h for description. */ ++int gasket_register_device(const struct gasket_driver_desc *driver_desc) ++{ ++ int i, ret; ++ int desc_idx = -1; ++ struct gasket_internal_desc *internal; ++ ++ pr_debug("Loading %s driver version %s\n", driver_desc->name, ++ driver_desc->driver_version); ++ /* Check for duplicates and find a free slot. */ ++ mutex_lock(&g_mutex); ++ ++ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { ++ if (g_descs[i].driver_desc == driver_desc) { ++ pr_err("%s driver already loaded/registered\n", ++ driver_desc->name); ++ mutex_unlock(&g_mutex); ++ return -EBUSY; ++ } ++ } ++ ++ /* This and the above loop could be combined, but this reads easier. */ ++ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { ++ if (!g_descs[i].driver_desc) { ++ g_descs[i].driver_desc = driver_desc; ++ desc_idx = i; ++ break; ++ } ++ } ++ mutex_unlock(&g_mutex); ++ ++ if (desc_idx == -1) { ++ pr_err("too many drivers loaded, max %d\n", ++ GASKET_FRAMEWORK_DESC_MAX); ++ return -EBUSY; ++ } ++ ++ internal = &g_descs[desc_idx]; ++ mutex_init(&internal->mutex); ++ memset(internal->devs, 0, sizeof(struct gasket_dev *) * GASKET_DEV_MAX); ++ ++ /* Function signature for `class_create()` is changed in kernel >= 6.4.x ++ * to only accept a single argument. ++ * */ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) ++ internal->class = class_create(driver_desc->module, driver_desc->name); ++#else ++ internal->class = class_create(driver_desc->name); ++#endif ++ ++ if (IS_ERR(internal->class)) { ++ pr_err("Cannot register %s class [ret=%ld]\n", ++ driver_desc->name, PTR_ERR(internal->class)); ++ ret = PTR_ERR(internal->class); ++ goto unregister_gasket_driver; ++ } ++ ++ ret = register_chrdev_region(MKDEV(driver_desc->major, ++ driver_desc->minor), GASKET_DEV_MAX, ++ driver_desc->name); ++ if (ret) { ++ pr_err("cannot register %s char driver [ret=%d]\n", ++ driver_desc->name, ret); ++ goto destroy_class; ++ } ++ ++ return 0; ++ ++destroy_class: ++ class_destroy(internal->class); ++ ++unregister_gasket_driver: ++ mutex_lock(&g_mutex); ++ g_descs[desc_idx].driver_desc = NULL; ++ mutex_unlock(&g_mutex); ++ return ret; ++} ++EXPORT_SYMBOL(gasket_register_device); ++ ++/* See gasket_core.h for description. */ ++void gasket_unregister_device(const struct gasket_driver_desc *driver_desc) ++{ ++ int i, desc_idx; ++ struct gasket_internal_desc *internal_desc = NULL; ++ ++ mutex_lock(&g_mutex); ++ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { ++ if (g_descs[i].driver_desc == driver_desc) { ++ internal_desc = &g_descs[i]; ++ desc_idx = i; ++ break; ++ } ++ } ++ ++ if (!internal_desc) { ++ mutex_unlock(&g_mutex); ++ pr_err("request to unregister unknown desc: %s, %d:%d\n", ++ driver_desc->name, driver_desc->major, ++ driver_desc->minor); ++ return; ++ } ++ ++ unregister_chrdev_region(MKDEV(driver_desc->major, driver_desc->minor), ++ GASKET_DEV_MAX); ++ ++ class_destroy(internal_desc->class); ++ ++ /* Finally, effectively "remove" the driver. */ ++ g_descs[desc_idx].driver_desc = NULL; ++ mutex_unlock(&g_mutex); ++ ++ pr_debug("removed %s driver\n", driver_desc->name); ++} ++EXPORT_SYMBOL(gasket_unregister_device); ++ ++static int __init gasket_init(void) ++{ ++ int i; ++ ++ mutex_lock(&g_mutex); ++ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) { ++ g_descs[i].driver_desc = NULL; ++ mutex_init(&g_descs[i].mutex); ++ } ++ ++ gasket_sysfs_init(); ++ ++ mutex_unlock(&g_mutex); ++ return 0; ++} ++ ++MODULE_DESCRIPTION("Google Gasket driver framework"); ++MODULE_VERSION(GASKET_FRAMEWORK_VERSION); ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Rob Springer "); ++module_init(gasket_init); +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_core.h b/drivers/staging/gasket-driver/gasket_core.h +--- a/drivers/staging/gasket-driver/gasket_core.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_core.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,657 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Gasket generic driver. Defines the set of data types and functions necessary ++ * to define a driver using the Gasket generic driver framework. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++#ifndef __GASKET_CORE_H__ ++#define __GASKET_CORE_H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "gasket_constants.h" ++ ++/** ++ * struct gasket_num_name - Map numbers to names. ++ * @ein_num: Number. ++ * @ein_name: Name associated with the number, a char pointer. ++ * ++ * This structure maps numbers to names. It is used to provide printable enum ++ * names, e.g {0, "DEAD"} or {1, "ALIVE"}. ++ */ ++struct gasket_num_name { ++ uint snn_num; ++ const char *snn_name; ++}; ++ ++/* ++ * Register location for packed interrupts. ++ * Each value indicates the location of an interrupt field (in units of ++ * gasket_driver_desc->interrupt_pack_width) within the containing register. ++ * In other words, this indicates the shift to use when creating a mask to ++ * extract/set bits within a register for a given interrupt. ++ */ ++enum gasket_interrupt_packing { ++ PACK_0 = 0, ++ PACK_1 = 1, ++ PACK_2 = 2, ++ PACK_3 = 3, ++ UNPACKED = 4, ++}; ++ ++/* Type of the interrupt supported by the device. */ ++enum gasket_interrupt_type { ++ PCI_MSIX = 0, ++ DEVICE_MANAGED = 1, /* Managed externally in device driver */ ++}; ++ ++/* ++ * Used to describe a Gasket interrupt. Contains an interrupt index, a register, ++ * and packing data for that interrupt. The register and packing data ++ * fields are relevant only for PCI_MSIX interrupt type and can be ++ * set to 0 for everything else. ++ */ ++struct gasket_interrupt_desc { ++ /* Device-wide interrupt index/number. */ ++ int index; ++ /* The register offset controlling this interrupt. */ ++ u64 reg; ++ /* The location of this interrupt inside register reg, if packed. */ ++ int packing; ++}; ++ ++/* ++ * This enum is used to identify memory regions being part of the physical ++ * memory that belongs to a device. ++ */ ++enum mappable_area_type { ++ PCI_BAR = 0, /* Default */ ++ BUS_REGION, /* For SYSBUS devices, i.e. AXI etc... */ ++ COHERENT_MEMORY ++}; ++ ++/* ++ * Metadata for each BAR mapping. ++ * This struct is used so as to track PCI memory, I/O space, AXI and coherent ++ * memory area... i.e. memory objects which can be referenced in the device's ++ * mmap function. ++ */ ++struct gasket_bar_data { ++ /* Virtual base address. */ ++ u8 __iomem *virt_base; ++ ++ /* Physical base address. */ ++ ulong phys_base; ++ ++ /* Length of the mapping. */ ++ ulong length_bytes; ++ ++ /* Type of mappable area */ ++ enum mappable_area_type type; ++}; ++ ++/* Maintains device open ownership data. */ ++struct gasket_ownership { ++ /* 1 if the device is owned, 0 otherwise. */ ++ int is_owned; ++ ++ /* TGID of the owner. */ ++ pid_t owner; ++ ++ /* Count of current device opens in write mode. */ ++ int write_open_count; ++}; ++ ++/* Page table modes of operation. */ ++enum gasket_page_table_mode { ++ /* The page table is partitionable as normal, all simple by default. */ ++ GASKET_PAGE_TABLE_MODE_NORMAL, ++ ++ /* All entries are always simple. */ ++ GASKET_PAGE_TABLE_MODE_SIMPLE, ++ ++ /* All entries are always extended. No extended bit is used. */ ++ GASKET_PAGE_TABLE_MODE_EXTENDED, ++}; ++ ++/* Page table configuration. One per table. */ ++struct gasket_page_table_config { ++ /* The identifier/index of this page table. */ ++ int id; ++ ++ /* The operation mode of this page table. */ ++ enum gasket_page_table_mode mode; ++ ++ /* Total (first-level) entries in this page table. */ ++ ulong total_entries; ++ ++ /* Base register for the page table. */ ++ int base_reg; ++ ++ /* ++ * Register containing the extended page table. This value is unused in ++ * GASKET_PAGE_TABLE_MODE_SIMPLE and GASKET_PAGE_TABLE_MODE_EXTENDED ++ * modes. ++ */ ++ int extended_reg; ++ ++ /* The bit index indicating whether a PT entry is extended. */ ++ int extended_bit; ++}; ++ ++/* Maintains information about a device node. */ ++struct gasket_cdev_info { ++ /* The internal name of this device. */ ++ char name[GASKET_NAME_MAX]; ++ ++ /* Device number. */ ++ dev_t devt; ++ ++ /* Kernel-internal device structure. */ ++ struct device *device; ++ ++ /* Character device for real. */ ++ struct cdev cdev; ++ ++ /* Flag indicating if cdev_add has been called for the devices. */ ++ int cdev_added; ++ ++ /* Pointer to the overall gasket_dev struct for this device. */ ++ struct gasket_dev *gasket_dev_ptr; ++ ++ /* Ownership data for the device in question. */ ++ struct gasket_ownership ownership; ++}; ++ ++/* Describes the offset and length of mmapable device BAR regions. */ ++struct gasket_mappable_region { ++ u64 start; ++ u64 length_bytes; ++}; ++ ++/* Describe the offset, size, and permissions for a device bar. */ ++struct gasket_bar_desc { ++ /* ++ * The size of each PCI BAR range, in bytes. If a value is 0, that BAR ++ * will not be mapped into kernel space at all. ++ * For devices with 64 bit BARs, only elements 0, 2, and 4 should be ++ * populated, and 1, 3, and 5 should be set to 0. ++ * For example, for a device mapping 1M in each of the first two 64-bit ++ * BARs, this field would be set as { 0x100000, 0, 0x100000, 0, 0, 0 } ++ * (one number per bar_desc struct.) ++ */ ++ u64 size; ++ /* The permissions for this bar. (Should be VM_WRITE/VM_READ/VM_EXEC, ++ * and can be or'd.) If set to GASKET_NOMAP, the bar will ++ * not be used for mmapping. ++ */ ++ ulong permissions; ++ /* The memory address corresponding to the base of this bar, if used. */ ++ u64 base; ++ /* The number of mappable regions in this bar. */ ++ int num_mappable_regions; ++ ++ /* The mappable subregions of this bar. */ ++ const struct gasket_mappable_region *mappable_regions; ++ ++ /* Type of mappable area */ ++ enum mappable_area_type type; ++}; ++ ++/* Describes the offset, size, and permissions for a coherent buffer. */ ++struct gasket_coherent_buffer_desc { ++ /* The size of the coherent buffer. */ ++ u64 size; ++ ++ /* The permissions for this bar. (Should be VM_WRITE/VM_READ/VM_EXEC, ++ * and can be or'd.) If set to GASKET_NOMAP, the bar will ++ * not be used for mmaping. ++ */ ++ ulong permissions; ++ ++ /* device side address. */ ++ u64 base; ++}; ++ ++/* Coherent buffer structure. */ ++struct gasket_coherent_buffer { ++ /* Virtual base address. */ ++ u8 __iomem *virt_base; ++ ++ /* Physical base address. */ ++ dma_addr_t phys_base; ++ ++ /* Length of the mapping. */ ++ ulong length_bytes; ++}; ++ ++/* Description of Gasket-specific permissions in the mmap field. */ ++enum gasket_mapping_options { GASKET_NOMAP = 0 }; ++ ++/* This struct represents an undefined bar that should never be mapped. */ ++#define GASKET_UNUSED_BAR \ ++ { \ ++ 0, GASKET_NOMAP, 0, 0, NULL, 0 \ ++ } ++ ++/* Internal data for a Gasket device. See gasket_core.c for more information. */ ++struct gasket_internal_desc; ++ ++#define MAX_NUM_COHERENT_PAGES 16 ++ ++/* ++ * Device data for Gasket device instances. ++ * ++ * This structure contains the data required to manage a Gasket device. ++ */ ++struct gasket_dev { ++ /* Pointer to the internal driver description for this device. */ ++ struct gasket_internal_desc *internal_desc; ++ ++ /* Device info */ ++ struct device *dev; ++ ++ /* DMA device to use, may be same as above or a parent */ ++ struct device *dma_dev; ++ ++ /* PCI device pointer for PCI devices */ ++ struct pci_dev *pci_dev; ++ ++ /* Platform device pointer for platform devices */ ++ struct platform_device *platform_dev; ++ ++ /* This device's index into internal_desc->devs. */ ++ int dev_idx; ++ ++ /* The name of this device, as reported by the kernel. */ ++ char kobj_name[GASKET_NAME_MAX]; ++ ++ /* Virtual address of mapped BAR memory range. */ ++ struct gasket_bar_data bar_data[GASKET_NUM_BARS]; ++ ++ /* Coherent buffer. */ ++ struct gasket_coherent_buffer coherent_buffer; ++ ++ /* Number of page tables for this device. */ ++ int num_page_tables; ++ ++ /* Address translations. Page tables have a private implementation. */ ++ struct gasket_page_table *page_table[GASKET_MAX_NUM_PAGE_TABLES]; ++ ++ /* Interrupt data for this device. */ ++ struct gasket_interrupt_data *interrupt_data; ++ ++ /* Status for this device - GASKET_STATUS_ALIVE or _DEAD. */ ++ uint status; ++ ++ /* Number of times this device has been reset. */ ++ uint reset_count; ++ ++ /* Dev information for the cdev node. */ ++ struct gasket_cdev_info dev_info; ++ ++ /* Hardware revision value for this device. */ ++ int hardware_revision; ++ ++ /* Protects access to per-device data (i.e. this structure). */ ++ struct mutex mutex; ++ ++ /* cdev hash tracking/membership structure, Accel and legacy. */ ++ /* Unused until Accel is upstreamed. */ ++ struct hlist_node hlist_node; ++ struct hlist_node legacy_hlist_node; ++}; ++ ++/* Type of the ioctl handler callback. */ ++typedef long (*gasket_ioctl_handler_cb_t)(struct file *file, uint cmd, ++ void __user *argp); ++/* Type of the ioctl permissions check callback. See below. */ ++typedef int (*gasket_ioctl_permissions_cb_t)(struct file *filp, uint cmd, ++ void __user *argp); ++ ++/* ++ * Device type descriptor. ++ * ++ * This structure contains device-specific data needed to identify and address a ++ * type of device to be administered via the Gasket generic driver. ++ * ++ * Device IDs are per-driver. In other words, two drivers using the Gasket ++ * framework will each have a distinct device 0 (for example). ++ */ ++struct gasket_driver_desc { ++ /* The name of this device type. */ ++ const char *name; ++ ++ /* The name of this specific device model. */ ++ const char *chip_model; ++ ++ /* The version of the chip specified in chip_model. */ ++ const char *chip_version; ++ ++ /* The version of this driver: "1.0.0", "2.1.3", etc. */ ++ const char *driver_version; ++ ++ /* ++ * Non-zero if we should create "legacy" (device and device-class- ++ * specific) character devices and sysfs nodes. ++ */ ++ /* Unused until Accel is upstreamed. */ ++ int legacy_support; ++ ++ /* Major and minor numbers identifying the device. */ ++ int major, minor; ++ ++ /* Module structure for this driver. */ ++ struct module *module; ++ ++ /* PCI ID table. */ ++ const struct pci_device_id *pci_id_table; ++ ++ /* The number of page tables handled by this driver. */ ++ int num_page_tables; ++ ++ /* The index of the bar containing the page tables. */ ++ int page_table_bar_index; ++ ++ /* Registers used to control each page table. */ ++ const struct gasket_page_table_config *page_table_configs; ++ ++ /* The bit index indicating whether a PT entry is extended. */ ++ int page_table_extended_bit; ++ ++ /* ++ * Legacy mmap address adjusment for legacy devices only. Should be 0 ++ * for any new device. ++ */ ++ ulong legacy_mmap_address_offset; ++ ++ /* Set of 6 bar descriptions that describe all PCIe bars. ++ * Note that BUS/AXI devices (i.e. non PCI devices) use those. ++ */ ++ struct gasket_bar_desc bar_descriptions[GASKET_NUM_BARS]; ++ ++ /* ++ * Coherent buffer description. ++ */ ++ struct gasket_coherent_buffer_desc coherent_buffer_description; ++ ++ /* Interrupt type. (One of gasket_interrupt_type). */ ++ int interrupt_type; ++ ++ /* Index of the bar containing the interrupt registers to program. */ ++ int interrupt_bar_index; ++ ++ /* Number of interrupts in the gasket_interrupt_desc array */ ++ int num_interrupts; ++ ++ /* Description of the interrupts for this device. */ ++ const struct gasket_interrupt_desc *interrupts; ++ ++ /* ++ * If this device packs multiple interrupt->MSI-X mappings into a ++ * single register (i.e., "uses packed interrupts"), only a single bit ++ * width is supported for each interrupt mapping (unpacked/"full-width" ++ * interrupts are always supported). This value specifies that width. If ++ * packed interrupts are not used, this value is ignored. ++ */ ++ int interrupt_pack_width; ++ ++ /* Driver callback functions - all may be NULL */ ++ /* ++ * device_open_cb: Callback for when a device node is opened in write ++ * mode. ++ * @dev: The gasket_dev struct for this driver instance. ++ * ++ * This callback should perform device-specific setup that needs to ++ * occur only once when a device is first opened. ++ */ ++ int (*device_open_cb)(struct gasket_dev *dev); ++ ++ /* ++ * device_release_cb: Callback when a device is closed. ++ * @gasket_dev: The gasket_dev struct for this driver instance. ++ * ++ * This callback is called whenever a device node fd is closed, as ++ * opposed to device_close_cb, which is called when the _last_ ++ * descriptor for an open file is closed. This call is intended to ++ * handle any per-user or per-fd cleanup. ++ */ ++ int (*device_release_cb)(struct gasket_dev *gasket_dev, ++ struct file *file); ++ ++ /* ++ * device_close_cb: Callback for when a device node is closed for the ++ * last time. ++ * @dev: The gasket_dev struct for this driver instance. ++ * ++ * This callback should perform device-specific cleanup that only ++ * needs to occur when the last reference to a device node is closed. ++ * ++ * This call is intended to handle and device-wide cleanup, as opposed ++ * to per-fd cleanup (which should be handled by device_release_cb). ++ */ ++ int (*device_close_cb)(struct gasket_dev *dev); ++ ++ /* ++ * get_mappable_regions_cb: Get descriptors of mappable device memory. ++ * @gasket_dev: Pointer to the struct gasket_dev for this device. ++ * @bar_index: BAR for which to retrieve memory ranges. ++ * @mappable_regions: Out-pointer to the list of mappable regions on the ++ * device/BAR for this process. ++ * @num_mappable_regions: Out-pointer for the size of mappable_regions. ++ * ++ * Called when handling mmap(), this callback is used to determine which ++ * regions of device memory may be mapped by the current process. This ++ * information is then compared to mmap request to determine which ++ * regions to actually map. ++ */ ++ int (*get_mappable_regions_cb)(struct gasket_dev *gasket_dev, ++ int bar_index, ++ struct gasket_mappable_region **mappable_regions, ++ int *num_mappable_regions); ++ ++ /* ++ * ioctl_permissions_cb: Check permissions for generic ioctls. ++ * @filp: File structure pointer describing this node usage session. ++ * @cmd: ioctl number to handle. ++ * @arg: ioctl-specific data pointer. ++ * ++ * Returns 1 if the ioctl may be executed, 0 otherwise. If this callback ++ * isn't specified a default routine will be used, that only allows the ++ * original device opener (i.e, the "owner") to execute state-affecting ++ * ioctls. ++ */ ++ gasket_ioctl_permissions_cb_t ioctl_permissions_cb; ++ ++ /* ++ * ioctl_handler_cb: Callback to handle device-specific ioctls. ++ * @filp: File structure pointer describing this node usage session. ++ * @cmd: ioctl number to handle. ++ * @arg: ioctl-specific data pointer. ++ * ++ * Invoked whenever an ioctl is called that the generic Gasket ++ * framework doesn't support. If no cb is registered, unknown ioctls ++ * return -EINVAL. Should return an error status (either -EINVAL or ++ * the error result of the ioctl being handled). ++ */ ++ gasket_ioctl_handler_cb_t ioctl_handler_cb; ++ ++ /* ++ * device_status_cb: Callback to determine device health. ++ * @dev: Pointer to the gasket_dev struct for this device. ++ * ++ * Called to determine if the device is healthy or not. Should return ++ * a member of the gasket_status_type enum. ++ * ++ */ ++ int (*device_status_cb)(struct gasket_dev *dev); ++ ++ /* ++ * hardware_revision_cb: Get the device's hardware revision. ++ * @dev: Pointer to the gasket_dev struct for this device. ++ * ++ * Called to determine the reported rev of the physical hardware. ++ * Revision should be >0. A negative return value is an error. ++ */ ++ int (*hardware_revision_cb)(struct gasket_dev *dev); ++ ++ /* ++ * device_reset_cb: Reset the hardware in question. ++ * @dev: Pointer to the gasket_dev structure for this device. ++ * ++ * Called by reset ioctls. This function should not ++ * lock the gasket_dev mutex. It should return 0 on success ++ * and an error on failure. ++ */ ++ int (*device_reset_cb)(struct gasket_dev *dev); ++}; ++ ++/* ++ * Register the specified device type with the framework. ++ * @desc: Populated/initialized device type descriptor. ++ * ++ * This function does _not_ take ownership of desc; the underlying struct must ++ * exist until the matching call to gasket_unregister_device. ++ * This function should be called from your driver's module_init function. ++ */ ++int gasket_register_device(const struct gasket_driver_desc *desc); ++ ++/* ++ * Remove the specified device type from the framework. ++ * @desc: Descriptor for the device type to unregister; it should have been ++ * passed to gasket_register_device in a previous call. ++ * ++ * This function should be called from your driver's module_exit function. ++ */ ++void gasket_unregister_device(const struct gasket_driver_desc *desc); ++ ++/* Add a PCI gasket device. */ ++int gasket_pci_add_device(struct pci_dev *pci_dev, ++ struct gasket_dev **gasket_devp); ++/* Remove a PCI gasket device. */ ++void gasket_pci_remove_device(struct pci_dev *pci_dev); ++ ++/* Add a platform gasket device. */ ++int gasket_platform_add_device(struct platform_device *pdev, ++ struct gasket_dev **gasket_devp); ++ ++/* Remove a platform gasket device. */ ++void gasket_platform_remove_device(struct platform_device *pdev); ++ ++/* Set DMA device to use (if different from PCI/platform device) */ ++void gasket_set_dma_device(struct gasket_dev *gasket_dev, ++ struct device *dma_dev); ++ ++/* Enable a Gasket device. */ ++int gasket_enable_device(struct gasket_dev *gasket_dev); ++ ++/* Disable a Gasket device. */ ++void gasket_disable_device(struct gasket_dev *gasket_dev); ++ ++/* ++ * Reset the Gasket device. ++ * @gasket_dev: Gasket device struct. ++ * ++ * Calls device_reset_cb. Returns 0 on success and an error code othewrise. ++ * gasket_reset_nolock will not lock the mutex, gasket_reset will. ++ * ++ */ ++int gasket_reset(struct gasket_dev *gasket_dev); ++int gasket_reset_nolock(struct gasket_dev *gasket_dev); ++ ++/* ++ * Memory management functions. These will likely be spun off into their own ++ * file in the future. ++ */ ++ ++/* Unmaps the specified mappable region from a VMA. */ ++int gasket_mm_unmap_region(const struct gasket_dev *gasket_dev, ++ struct vm_area_struct *vma, ++ const struct gasket_mappable_region *map_region); ++ ++/* ++ * Get the ioctl permissions callback. ++ * @gasket_dev: Gasket device structure. ++ */ ++gasket_ioctl_permissions_cb_t ++gasket_get_ioctl_permissions_cb(struct gasket_dev *gasket_dev); ++ ++/** ++ * Lookup a name by number in a num_name table. ++ * @num: Number to lookup. ++ * @table: Array of num_name structures, the table for the lookup. ++ * ++ */ ++const char *gasket_num_name_lookup(uint num, ++ const struct gasket_num_name *table); ++ ++/* Handy inlines */ ++static inline u64 gasket_dev_read_64(struct gasket_dev *gasket_dev, int bar, ++ ulong location) ++{ ++ return readq_relaxed(&gasket_dev->bar_data[bar].virt_base[location]); ++} ++ ++static inline void gasket_dev_write_64(struct gasket_dev *dev, u64 value, ++ int bar, ulong location) ++{ ++ writeq_relaxed(value, &dev->bar_data[bar].virt_base[location]); ++} ++ ++static inline void gasket_dev_write_32(struct gasket_dev *dev, u32 value, ++ int bar, ulong location) ++{ ++ writel_relaxed(value, &dev->bar_data[bar].virt_base[location]); ++} ++ ++static inline u32 gasket_dev_read_32(struct gasket_dev *dev, int bar, ++ ulong location) ++{ ++ return readl_relaxed(&dev->bar_data[bar].virt_base[location]); ++} ++ ++static inline void gasket_read_modify_write_64(struct gasket_dev *dev, int bar, ++ ulong location, u64 value, ++ u64 mask_width, u64 mask_shift) ++{ ++ u64 mask, tmp; ++ ++ tmp = gasket_dev_read_64(dev, bar, location); ++ mask = ((1ULL << mask_width) - 1) << mask_shift; ++ tmp = (tmp & ~mask) | (value << mask_shift); ++ gasket_dev_write_64(dev, tmp, bar, location); ++} ++ ++static inline void gasket_read_modify_write_32(struct gasket_dev *dev, int bar, ++ ulong location, u32 value, ++ u32 mask_width, u32 mask_shift) ++{ ++ u32 mask, tmp; ++ ++ tmp = gasket_dev_read_32(dev, bar, location); ++ mask = ((1 << mask_width) - 1) << mask_shift; ++ tmp = (tmp & ~mask) | (value << mask_shift); ++ gasket_dev_write_32(dev, tmp, bar, location); ++} ++ ++/* Get the Gasket driver structure for a given device. */ ++const struct gasket_driver_desc *gasket_get_driver_desc(struct gasket_dev *dev); ++ ++/* Get the device structure for a given device. */ ++struct device *gasket_get_device(struct gasket_dev *dev); ++ ++/* Helper function, Asynchronous waits on a given set of bits. */ ++int gasket_wait_with_reschedule(struct gasket_dev *gasket_dev, int bar, ++ u64 offset, u64 mask, u64 val, ++ uint max_retries, u64 delay_ms); ++ ++#endif /* __GASKET_CORE_H__ */ +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket.h b/drivers/staging/gasket-driver/gasket.h +--- a/drivers/staging/gasket-driver/gasket.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,177 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Common Gasket device kernel and user space declarations. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++#ifndef __GASKET_H__ ++#define __GASKET_H__ ++ ++#include ++#include ++ ++/* ioctl structure declarations */ ++ ++/* Ioctl structures are padded to a multiple of 64 bits */ ++/* and padded to put 64 bit values on 64 bit boundaries. */ ++/* Unsigned 64 bit integers are used to hold pointers. */ ++/* This helps compatibility between 32 and 64 bits. */ ++ ++/* ++ * Common structure for ioctls associating an eventfd with a device interrupt, ++ * when using the Gasket interrupt module. ++ */ ++struct gasket_interrupt_eventfd { ++ u64 interrupt; ++ u64 event_fd; ++}; ++ ++/* ++ * Common structure for ioctls mapping and unmapping buffers when using the ++ * Gasket page_table module. ++ */ ++struct gasket_page_table_ioctl { ++ u64 page_table_index; ++ u64 size; ++ u64 host_address; ++ u64 device_address; ++}; ++ ++/* ++ * Structure for ioctl mapping buffers with flags when using the Gasket ++ * page_table module. ++ */ ++struct gasket_page_table_ioctl_flags { ++ struct gasket_page_table_ioctl base; ++ /* ++ * Flags indicating status and attribute requests from the host. ++ * NOTE: STATUS bit does not need to be set in this request. ++ * Set RESERVED bits to 0 to ensure backwards compatibility. ++ * ++ * Bitfields: ++ * [0] - STATUS: indicates if this entry/slot is free ++ * 0 = PTE_FREE ++ * 1 = PTE_INUSE ++ * [2:1] - DMA_DIRECTION: dma_data_direction requested by host ++ * 00 = DMA_BIDIRECTIONAL ++ * 01 = DMA_TO_DEVICE ++ * 10 = DMA_FROM_DEVICE ++ * 11 = DMA_NONE ++ * [31:3] - RESERVED ++ */ ++ u32 flags; ++}; ++ ++/* ++ * Common structure for ioctls mapping and unmapping buffers when using the ++ * Gasket page_table module. ++ * dma_address: phys addr start of coherent memory, allocated by kernel ++ */ ++struct gasket_coherent_alloc_config_ioctl { ++ u64 page_table_index; ++ u64 enable; ++ u64 size; ++ u64 dma_address; ++}; ++ ++/* ++ * Common structure for ioctls mapping and unmapping dma-bufs when using the ++ * Gasket page_table module. ++ * map: boolean, non-zero to map, 0 to unmap. ++ * flags: see gasket_page_table_ioctl_flags.flags. ++ */ ++struct gasket_page_table_ioctl_dmabuf { ++ u64 page_table_index; ++ u64 device_address; ++ int dmabuf_fd; ++ u32 num_pages; ++ u32 map; ++ u32 flags; ++}; ++ ++/* Base number for all Gasket-common IOCTLs */ ++#define GASKET_IOCTL_BASE 0xDC ++ ++/* Reset the device. */ ++#define GASKET_IOCTL_RESET _IO(GASKET_IOCTL_BASE, 0) ++ ++/* Associate the specified [event]fd with the specified interrupt. */ ++#define GASKET_IOCTL_SET_EVENTFD \ ++ _IOW(GASKET_IOCTL_BASE, 1, struct gasket_interrupt_eventfd) ++ ++/* ++ * Clears any eventfd associated with the specified interrupt. The (ulong) ++ * argument is the interrupt number to clear. ++ */ ++#define GASKET_IOCTL_CLEAR_EVENTFD _IOW(GASKET_IOCTL_BASE, 2, unsigned long) ++ ++/* ++ * [Loopbacks only] Requests that the loopback device send the specified ++ * interrupt to the host. The (ulong) argument is the number of the interrupt to ++ * send. ++ */ ++#define GASKET_IOCTL_LOOPBACK_INTERRUPT \ ++ _IOW(GASKET_IOCTL_BASE, 3, unsigned long) ++ ++/* Queries the kernel for the number of page tables supported by the device. */ ++#define GASKET_IOCTL_NUMBER_PAGE_TABLES _IOR(GASKET_IOCTL_BASE, 4, u64) ++ ++/* ++ * Queries the kernel for the maximum size of the page table. Only the size and ++ * page_table_index fields are used from the struct gasket_page_table_ioctl. ++ */ ++#define GASKET_IOCTL_PAGE_TABLE_SIZE \ ++ _IOWR(GASKET_IOCTL_BASE, 5, struct gasket_page_table_ioctl) ++ ++/* ++ * Queries the kernel for the current simple page table size. Only the size and ++ * page_table_index fields are used from the struct gasket_page_table_ioctl. ++ */ ++#define GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE \ ++ _IOWR(GASKET_IOCTL_BASE, 6, struct gasket_page_table_ioctl) ++ ++/* ++ * Tells the kernel to change the split between the number of simple and ++ * extended entries in the given page table. Only the size and page_table_index ++ * fields are used from the struct gasket_page_table_ioctl. ++ */ ++#define GASKET_IOCTL_PARTITION_PAGE_TABLE \ ++ _IOW(GASKET_IOCTL_BASE, 7, struct gasket_page_table_ioctl) ++ ++/* ++ * Tells the kernel to map size bytes at host_address to device_address in ++ * page_table_index page table. ++ */ ++#define GASKET_IOCTL_MAP_BUFFER \ ++ _IOW(GASKET_IOCTL_BASE, 8, struct gasket_page_table_ioctl) ++ ++/* ++ * Tells the kernel to unmap size bytes at host_address from device_address in ++ * page_table_index page table. ++ */ ++#define GASKET_IOCTL_UNMAP_BUFFER \ ++ _IOW(GASKET_IOCTL_BASE, 9, struct gasket_page_table_ioctl) ++ ++/* Clear the interrupt counts stored for this device. */ ++#define GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS _IO(GASKET_IOCTL_BASE, 10) ++ ++/* Enable/Disable and configure the coherent allocator. */ ++#define GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR \ ++ _IOWR(GASKET_IOCTL_BASE, 11, struct gasket_coherent_alloc_config_ioctl) ++ ++/* ++ * Tells the kernel to map size bytes at host_address to device_address in ++ * page_table_index page table. Passes flags to indicate additional attribute ++ * requests for the mapped memory. ++ */ ++#define GASKET_IOCTL_MAP_BUFFER_FLAGS \ ++ _IOW(GASKET_IOCTL_BASE, 12, struct gasket_page_table_ioctl_flags) ++ ++/* ++ * Tells the kernel to map/unmap dma-buf with fd to device_address in ++ * page_table_index page table. ++ */ ++#define GASKET_IOCTL_MAP_DMABUF \ ++ _IOW(GASKET_IOCTL_BASE, 13, struct gasket_page_table_ioctl_dmabuf) ++ ++#endif /* __GASKET_H__ */ +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_interrupt.c b/drivers/staging/gasket-driver/gasket_interrupt.c +--- a/drivers/staging/gasket-driver/gasket_interrupt.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_interrupt.c 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,559 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright (C) 2018 Google, Inc. */ ++ ++#include "gasket_interrupt.h" ++ ++#include "gasket_constants.h" ++#include "gasket_core.h" ++#include "gasket_sysfs.h" ++#include ++#include ++#include ++#include ++#include ++#ifdef GASKET_KERNEL_TRACE_SUPPORT ++#define CREATE_TRACE_POINTS ++#include ++#else ++#define trace_gasket_interrupt_event(x, ...) ++#endif ++/* Retry attempts if the requested number of interrupts aren't available. */ ++#define MSIX_RETRY_COUNT 3 ++ ++/* Instance interrupt management data. */ ++struct gasket_interrupt_data { ++ /* The name associated with this interrupt data. */ ++ const char *name; ++ ++ /* Interrupt type. See gasket_interrupt_type in gasket_core.h */ ++ int type; ++ ++ /* The PCI device [if any] associated with the owning device. */ ++ struct pci_dev *pci_dev; ++ ++ /* Set to 1 if MSI-X has successfully been configred, 0 otherwise. */ ++ int msix_configured; ++ ++ /* The number of interrupts requested by the owning device. */ ++ int num_interrupts; ++ ++ /* A pointer to the interrupt descriptor struct for this device. */ ++ const struct gasket_interrupt_desc *interrupts; ++ ++ /* The index of the bar into which interrupts should be mapped. */ ++ int interrupt_bar_index; ++ ++ /* The width of a single interrupt in a packed interrupt register. */ ++ int pack_width; ++ ++ /* ++ * Design-wise, these elements should be bundled together, but ++ * pci_enable_msix's interface requires that they be managed ++ * individually (requires array of struct msix_entry). ++ */ ++ ++ /* The number of successfully configured interrupts. */ ++ int num_configured; ++ ++ /* The MSI-X data for each requested/configured interrupt. */ ++ struct msix_entry *msix_entries; ++ ++ /* The eventfd "callback" data for each interrupt. */ ++ struct eventfd_ctx **eventfd_ctxs; ++ ++ /* Spinlock to protect read/write races to eventfd_ctxs. */ ++ rwlock_t eventfd_ctx_lock; ++ ++ /* The number of times each interrupt has been called. */ ++ ulong *interrupt_counts; ++ ++ /* Linux IRQ number. */ ++ int irq; ++}; ++ ++/* Structures to display interrupt counts in sysfs. */ ++enum interrupt_sysfs_attribute_type { ++ ATTR_INTERRUPT_COUNTS, ++}; ++ ++/* Set up device registers for interrupt handling. */ ++static void gasket_interrupt_setup(struct gasket_dev *gasket_dev) ++{ ++ int i; ++ int pack_shift; ++ u64 mask; ++ u64 value; ++ struct gasket_interrupt_data *interrupt_data = ++ gasket_dev->interrupt_data; ++ ++ if (!interrupt_data) { ++ dev_dbg(gasket_dev->dev, "Interrupt data is not initialized\n"); ++ return; ++ } ++ ++ dev_dbg(gasket_dev->dev, "Running interrupt setup\n"); ++ ++ if (interrupt_data->type == DEVICE_MANAGED) ++ return; /* device driver handles setup */ ++ ++ /* Setup the MSIX table. */ ++ ++ for (i = 0; i < interrupt_data->num_interrupts; i++) { ++ /* ++ * If the interrupt is not packed, we can write the index into ++ * the register directly. If not, we need to deal with a read- ++ * modify-write and shift based on the packing index. ++ */ ++ dev_dbg(gasket_dev->dev, ++ "Setting up interrupt index %d with index 0x%llx and " ++ "packing %d\n", ++ interrupt_data->interrupts[i].index, ++ interrupt_data->interrupts[i].reg, ++ interrupt_data->interrupts[i].packing); ++ if (interrupt_data->interrupts[i].packing == UNPACKED) { ++ value = interrupt_data->interrupts[i].index; ++ } else { ++ switch (interrupt_data->interrupts[i].packing) { ++ case PACK_0: ++ pack_shift = 0; ++ break; ++ case PACK_1: ++ pack_shift = interrupt_data->pack_width; ++ break; ++ case PACK_2: ++ pack_shift = 2 * interrupt_data->pack_width; ++ break; ++ case PACK_3: ++ pack_shift = 3 * interrupt_data->pack_width; ++ break; ++ default: ++ dev_dbg(gasket_dev->dev, ++ "Found interrupt description with " ++ "unknown enum %d\n", ++ interrupt_data->interrupts[i].packing); ++ return; ++ } ++ ++ mask = ~(0xFFFF << pack_shift); ++ value = gasket_dev_read_64(gasket_dev, ++ interrupt_data->interrupt_bar_index, ++ interrupt_data->interrupts[i].reg); ++ value &= mask; ++ value |= interrupt_data->interrupts[i].index ++ << pack_shift; ++ } ++ gasket_dev_write_64(gasket_dev, value, ++ interrupt_data->interrupt_bar_index, ++ interrupt_data->interrupts[i].reg); ++ } ++} ++ ++void ++gasket_handle_interrupt(struct gasket_interrupt_data *interrupt_data, ++ int interrupt_index) ++{ ++ struct eventfd_ctx *ctx; ++ ++ trace_gasket_interrupt_event(interrupt_data->name, interrupt_index); ++ read_lock(&interrupt_data->eventfd_ctx_lock); ++ ctx = interrupt_data->eventfd_ctxs[interrupt_index]; ++ if (ctx) ++ #if LINUX_VERSION_CODE >= KERNEL_VERSION(6,8,0) ++ eventfd_signal(ctx); ++ #else ++ eventfd_signal(ctx, 1); ++ #endif ++ read_unlock(&interrupt_data->eventfd_ctx_lock); ++ ++ ++(interrupt_data->interrupt_counts[interrupt_index]); ++} ++ ++static irqreturn_t gasket_msix_interrupt_handler(int irq, void *dev_id) ++{ ++ struct gasket_interrupt_data *interrupt_data = dev_id; ++ int interrupt = -1; ++ int i; ++ ++ /* If this linear lookup is a problem, we can maintain a map/hash. */ ++ for (i = 0; i < interrupt_data->num_interrupts; i++) { ++ if (interrupt_data->msix_entries[i].vector == irq) { ++ interrupt = interrupt_data->msix_entries[i].entry; ++ break; ++ } ++ } ++ if (interrupt == -1) { ++ pr_err("Received unknown irq %d\n", irq); ++ return IRQ_HANDLED; ++ } ++ gasket_handle_interrupt(interrupt_data, interrupt); ++ return IRQ_HANDLED; ++} ++ ++static int ++gasket_interrupt_msix_init(struct gasket_interrupt_data *interrupt_data) ++{ ++ int ret = 1; ++ int i; ++ ++ interrupt_data->msix_entries = ++ kcalloc(interrupt_data->num_interrupts, ++ sizeof(struct msix_entry), GFP_KERNEL); ++ if (!interrupt_data->msix_entries) ++ return -ENOMEM; ++ ++ for (i = 0; i < interrupt_data->num_interrupts; i++) { ++ interrupt_data->msix_entries[i].entry = i; ++ interrupt_data->msix_entries[i].vector = 0; ++ interrupt_data->eventfd_ctxs[i] = NULL; ++ } ++ ++ /* Retry MSIX_RETRY_COUNT times if not enough IRQs are available. */ ++ for (i = 0; i < MSIX_RETRY_COUNT && ret > 0; i++) ++ ret = pci_enable_msix_exact(interrupt_data->pci_dev, ++ interrupt_data->msix_entries, ++ interrupt_data->num_interrupts); ++ ++ if (ret) ++ return ret > 0 ? -EBUSY : ret; ++ interrupt_data->msix_configured = 1; ++ ++ for (i = 0; i < interrupt_data->num_interrupts; i++) { ++ ret = request_irq(interrupt_data->msix_entries[i].vector, ++ gasket_msix_interrupt_handler, 0, ++ interrupt_data->name, interrupt_data); ++ ++ if (ret) { ++ dev_err(&interrupt_data->pci_dev->dev, ++ "Cannot get IRQ for interrupt %d, vector %d; " ++ "%d\n", ++ i, interrupt_data->msix_entries[i].vector, ret); ++ return ret; ++ } ++ ++ interrupt_data->num_configured++; ++ } ++ ++ return 0; ++} ++ ++/* ++ * On QCM DragonBoard, we exit gasket_interrupt_msix_init() and kernel interrupt ++ * setup code with MSIX vectors masked. This is wrong because nothing else in ++ * the driver will normally touch the MSIX vectors. ++ * ++ * As a temporary hack, force unmasking there. ++ * ++ * TODO: Figure out why QCM kernel doesn't unmask the MSIX vectors, after ++ * gasket_interrupt_msix_init(), and remove this code. ++ */ ++static void force_msix_interrupt_unmasking(struct gasket_dev *gasket_dev) ++{ ++ int i; ++#define MSIX_VECTOR_SIZE 16 ++#define MSIX_MASK_BIT_OFFSET 12 ++#define APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE 0x46800 ++ for (i = 0; i < gasket_dev->interrupt_data->num_configured; i++) { ++ /* Check if the MSIX vector is unmasked */ ++ ulong location = APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE + ++ MSIX_MASK_BIT_OFFSET + i * MSIX_VECTOR_SIZE; ++ u32 mask = ++ gasket_dev_read_32(gasket_dev, ++ gasket_dev->interrupt_data->interrupt_bar_index, ++ location); ++ if (!(mask & 1)) ++ continue; ++ /* Unmask the msix vector (clear 32 bits) */ ++ gasket_dev_write_32(gasket_dev, 0, ++ gasket_dev->interrupt_data->interrupt_bar_index, ++ location); ++ } ++#undef MSIX_VECTOR_SIZE ++#undef MSIX_MASK_BIT_OFFSET ++#undef APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE ++} ++ ++static ssize_t interrupt_sysfs_show(struct device *device, ++ struct device_attribute *attr, char *buf) ++{ ++ int i, ret; ++ ssize_t written = 0, total_written = 0; ++ struct gasket_interrupt_data *interrupt_data; ++ struct gasket_dev *gasket_dev; ++ struct gasket_sysfs_attribute *gasket_attr; ++ enum interrupt_sysfs_attribute_type sysfs_type; ++ ++ gasket_dev = gasket_sysfs_get_device_data(device); ++ if (!gasket_dev) { ++ dev_dbg(device, "No sysfs mapping found for device\n"); ++ return 0; ++ } ++ ++ gasket_attr = gasket_sysfs_get_attr(device, attr); ++ if (!gasket_attr) { ++ dev_dbg(device, "No sysfs attr data found for device\n"); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return 0; ++ } ++ ++ sysfs_type = (enum interrupt_sysfs_attribute_type) ++ gasket_attr->data.attr_type; ++ interrupt_data = gasket_dev->interrupt_data; ++ switch (sysfs_type) { ++ case ATTR_INTERRUPT_COUNTS: ++ for (i = 0; i < interrupt_data->num_interrupts; ++i) { ++ written = ++ scnprintf(buf, PAGE_SIZE - total_written, ++ "0x%02x: %ld\n", i, ++ interrupt_data->interrupt_counts[i]); ++ total_written += written; ++ buf += written; ++ } ++ ret = total_written; ++ break; ++ default: ++ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n", ++ attr->attr.name); ++ ret = 0; ++ break; ++ } ++ ++ gasket_sysfs_put_attr(device, gasket_attr); ++ gasket_sysfs_put_device_data(device, gasket_dev); ++ return ret; ++} ++ ++static struct gasket_sysfs_attribute interrupt_sysfs_attrs[] = { ++ GASKET_SYSFS_RO(interrupt_counts, interrupt_sysfs_show, ++ ATTR_INTERRUPT_COUNTS), ++ GASKET_END_OF_ATTR_ARRAY, ++}; ++ ++int gasket_interrupt_init(struct gasket_dev *gasket_dev) ++{ ++ int ret; ++ struct gasket_interrupt_data *interrupt_data; ++ const struct gasket_driver_desc *driver_desc = ++ gasket_get_driver_desc(gasket_dev); ++ ++ interrupt_data = kzalloc(sizeof(struct gasket_interrupt_data), ++ GFP_KERNEL); ++ if (!interrupt_data) ++ return -ENOMEM; ++ gasket_dev->interrupt_data = interrupt_data; ++ interrupt_data->name = driver_desc->name; ++ interrupt_data->type = driver_desc->interrupt_type; ++ interrupt_data->pci_dev = gasket_dev->pci_dev; ++ interrupt_data->num_interrupts = driver_desc->num_interrupts; ++ interrupt_data->interrupts = driver_desc->interrupts; ++ interrupt_data->interrupt_bar_index = driver_desc->interrupt_bar_index; ++ interrupt_data->pack_width = driver_desc->interrupt_pack_width; ++ ++ interrupt_data->eventfd_ctxs = kcalloc(driver_desc->num_interrupts, ++ sizeof(struct eventfd_ctx *), ++ GFP_KERNEL); ++ if (!interrupt_data->eventfd_ctxs) { ++ kfree(interrupt_data); ++ return -ENOMEM; ++ } ++ ++ interrupt_data->interrupt_counts = kcalloc(driver_desc->num_interrupts, ++ sizeof(ulong), ++ GFP_KERNEL); ++ if (!interrupt_data->interrupt_counts) { ++ kfree(interrupt_data->eventfd_ctxs); ++ kfree(interrupt_data); ++ return -ENOMEM; ++ } ++ ++ rwlock_init(&interrupt_data->eventfd_ctx_lock); ++ ++ switch (interrupt_data->type) { ++ case PCI_MSIX: ++ ret = gasket_interrupt_msix_init(interrupt_data); ++ if (ret) ++ break; ++ force_msix_interrupt_unmasking(gasket_dev); ++ break; ++ ++ case DEVICE_MANAGED: /* Device driver manages IRQ init */ ++ interrupt_data->num_configured = interrupt_data->num_interrupts; ++ ret = 0; ++ break; ++ ++ default: ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ /* Failing to setup interrupts will cause the device to report ++ * GASKET_STATUS_LAMED. But it is not fatal. ++ */ ++ dev_warn(gasket_dev->dev, ++ "Couldn't initialize interrupts: %d\n", ret); ++ return 0; ++ } ++ ++ gasket_interrupt_setup(gasket_dev); ++ gasket_sysfs_create_entries(gasket_dev->dev_info.device, ++ interrupt_sysfs_attrs); ++ ++ return 0; ++} ++EXPORT_SYMBOL(gasket_interrupt_init); ++ ++void gasket_interrupt_msix_cleanup(struct gasket_interrupt_data *interrupt_data) ++{ ++ int i; ++ ++ for (i = 0; i < interrupt_data->num_configured; i++) { ++ gasket_interrupt_clear_eventfd(interrupt_data, i); ++ free_irq(interrupt_data->msix_entries[i].vector, ++ interrupt_data); ++ } ++ interrupt_data->num_configured = 0; ++ ++ if (interrupt_data->msix_configured) ++ pci_disable_msix(interrupt_data->pci_dev); ++ interrupt_data->msix_configured = 0; ++ kfree(interrupt_data->msix_entries); ++ interrupt_data->msix_entries = NULL; ++} ++EXPORT_SYMBOL(gasket_interrupt_msix_cleanup); ++ ++int gasket_interrupt_reinit(struct gasket_dev *gasket_dev) ++{ ++ int ret; ++ ++ if (!gasket_dev->interrupt_data) { ++ dev_dbg(gasket_dev->dev, ++ "Attempted to reinit uninitialized interrupt data\n"); ++ return -EINVAL; ++ } ++ ++ switch (gasket_dev->interrupt_data->type) { ++ case PCI_MSIX: ++ gasket_interrupt_msix_cleanup(gasket_dev->interrupt_data); ++ ret = gasket_interrupt_msix_init(gasket_dev->interrupt_data); ++ if (ret) ++ break; ++ force_msix_interrupt_unmasking(gasket_dev); ++ break; ++ ++ case DEVICE_MANAGED: /* Device driver manages IRQ reinit */ ++ ret = 0; ++ break; ++ ++ default: ++ ret = -EINVAL; ++ } ++ ++ if (ret) { ++ /* Failing to setup interrupts will cause the device ++ * to report GASKET_STATUS_LAMED, but is not fatal. ++ */ ++ dev_warn(gasket_dev->dev, "Couldn't reinit interrupts: %d\n", ++ ret); ++ return 0; ++ } ++ ++ gasket_interrupt_setup(gasket_dev); ++ ++ return 0; ++} ++EXPORT_SYMBOL(gasket_interrupt_reinit); ++ ++/* See gasket_interrupt.h for description. */ ++int gasket_interrupt_reset_counts(struct gasket_dev *gasket_dev) ++{ ++ dev_dbg(gasket_dev->dev, "Clearing interrupt counts\n"); ++ memset(gasket_dev->interrupt_data->interrupt_counts, 0, ++ gasket_dev->interrupt_data->num_interrupts * ++ sizeof(*gasket_dev->interrupt_data->interrupt_counts)); ++ return 0; ++} ++ ++/* See gasket_interrupt.h for description. */ ++void gasket_interrupt_cleanup(struct gasket_dev *gasket_dev) ++{ ++ struct gasket_interrupt_data *interrupt_data = ++ gasket_dev->interrupt_data; ++ /* ++ * It is possible to get an error code from gasket_interrupt_init ++ * before interrupt_data has been allocated, so check it. ++ */ ++ if (!interrupt_data) ++ return; ++ ++ switch (interrupt_data->type) { ++ case PCI_MSIX: ++ gasket_interrupt_msix_cleanup(interrupt_data); ++ break; ++ ++ case DEVICE_MANAGED: /* Device driver manages IRQ cleanup */ ++ break; ++ ++ default: ++ break; ++ } ++ ++ kfree(interrupt_data->interrupt_counts); ++ kfree(interrupt_data->eventfd_ctxs); ++ kfree(interrupt_data); ++ gasket_dev->interrupt_data = NULL; ++} ++ ++int gasket_interrupt_system_status(struct gasket_dev *gasket_dev) ++{ ++ if (!gasket_dev->interrupt_data) { ++ dev_dbg(gasket_dev->dev, "Interrupt data is null\n"); ++ return GASKET_STATUS_DEAD; ++ } ++ ++ if (gasket_dev->interrupt_data->num_configured != ++ gasket_dev->interrupt_data->num_interrupts) { ++ dev_dbg(gasket_dev->dev, ++ "Not all interrupts were configured\n"); ++ return GASKET_STATUS_LAMED; ++ } ++ ++ return GASKET_STATUS_ALIVE; ++} ++ ++int gasket_interrupt_set_eventfd(struct gasket_interrupt_data *interrupt_data, ++ int interrupt, int event_fd) ++{ ++ struct eventfd_ctx *ctx; ++ ulong flags; ++ ++ if (interrupt < 0 || interrupt >= interrupt_data->num_interrupts) ++ return -EINVAL; ++ ++ ctx = eventfd_ctx_fdget(event_fd); ++ if (IS_ERR(ctx)) ++ return PTR_ERR(ctx); ++ ++ /* Put the old eventfd ctx before setting, else we leak the ref. */ ++ write_lock_irqsave(&interrupt_data->eventfd_ctx_lock, flags); ++ if (interrupt_data->eventfd_ctxs[interrupt] != NULL) ++ eventfd_ctx_put(interrupt_data->eventfd_ctxs[interrupt]); ++ interrupt_data->eventfd_ctxs[interrupt] = ctx; ++ write_unlock_irqrestore(&interrupt_data->eventfd_ctx_lock, flags); ++ return 0; ++} ++ ++int gasket_interrupt_clear_eventfd(struct gasket_interrupt_data *interrupt_data, ++ int interrupt) ++{ ++ ulong flags; ++ ++ if (interrupt < 0 || interrupt >= interrupt_data->num_interrupts) ++ return -EINVAL; ++ ++ /* Put the old eventfd ctx before clearing, else we leak the ref. */ ++ write_lock_irqsave(&interrupt_data->eventfd_ctx_lock, flags); ++ if (interrupt_data->eventfd_ctxs[interrupt] != NULL) ++ eventfd_ctx_put(interrupt_data->eventfd_ctxs[interrupt]); ++ interrupt_data->eventfd_ctxs[interrupt] = NULL; ++ write_unlock_irqrestore(&interrupt_data->eventfd_ctx_lock, flags); ++ return 0; ++} +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_interrupt.h b/drivers/staging/gasket-driver/gasket_interrupt.h +--- a/drivers/staging/gasket-driver/gasket_interrupt.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_interrupt.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,108 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Gasket common interrupt module. Defines functions for enabling ++ * eventfd-triggered interrupts between a Gasket device and a host process. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++#ifndef __GASKET_INTERRUPT_H__ ++#define __GASKET_INTERRUPT_H__ ++ ++#include ++#include ++ ++#include "gasket_core.h" ++ ++/* Note that this currently assumes that device interrupts are a dense set, ++ * numbered from 0 - (num_interrupts - 1). Should this have to change, these ++ * APIs will have to be updated. ++ */ ++ ++/* Opaque type used to hold interrupt subsystem data. */ ++struct gasket_interrupt_data; ++ ++/* ++ * Initialize the interrupt module. ++ * @gasket_dev: The Gasket device structure for the device to be initted. ++ */ ++int gasket_interrupt_init(struct gasket_dev *gasket_dev); ++ ++/* ++ * Clean up a device's interrupt structure. ++ * @gasket_dev: The Gasket information structure for this device. ++ * ++ * Cleans up the device's interrupts and deallocates data. ++ */ ++void gasket_interrupt_cleanup(struct gasket_dev *gasket_dev); ++ ++/* ++ * Clean up and re-initialize the MSI-x subsystem. ++ * @gasket_dev: The Gasket information structure for this device. ++ * ++ * Performs a teardown of the MSI-x subsystem and re-initializes it. Does not ++ * free the underlying data structures. Returns 0 on success and an error code ++ * on error. ++ */ ++int gasket_interrupt_reinit(struct gasket_dev *gasket_dev); ++ ++/* ++ * Clean up the MSI-x subsystem. ++ * @interrupt_data: The interrupt data structure for this device. ++ * ++ * Performs a teardown of the MSI-x subsystem. Does not free the underlying data structures. ++ */ ++void gasket_interrupt_msix_cleanup(struct gasket_interrupt_data *interrupt_data); ++ ++/* Handle gasket interrupt processing, called from an external handler. */ ++void ++gasket_handle_interrupt(struct gasket_interrupt_data *interrupt_data, ++ int interrupt_index); ++ ++/* ++ * Reset the counts stored in the interrupt subsystem. ++ * @gasket_dev: The Gasket information structure for this device. ++ * ++ * Sets the counts of all interrupts in the subsystem to 0. ++ */ ++int gasket_interrupt_reset_counts(struct gasket_dev *gasket_dev); ++ ++/* ++ * Associates an eventfd with a device interrupt. ++ * @data: Pointer to device interrupt data. ++ * @interrupt: The device interrupt to configure. ++ * @event_fd: The eventfd to associate with the interrupt. ++ * ++ * Prepares the host to receive notification of device interrupts by associating ++ * event_fd with interrupt. Upon receipt of a device interrupt, event_fd will be ++ * signaled, after successful configuration. ++ * ++ * Returns 0 on success, a negative error code otherwise. ++ */ ++int gasket_interrupt_set_eventfd(struct gasket_interrupt_data *interrupt_data, ++ int interrupt, int event_fd); ++ ++/* ++ * Removes an interrupt-eventfd association. ++ * @data: Pointer to device interrupt data. ++ * @interrupt: The device interrupt to de-associate. ++ * ++ * Removes any eventfd associated with the specified interrupt, if any. ++ */ ++int gasket_interrupt_clear_eventfd(struct gasket_interrupt_data *interrupt_data, ++ int interrupt); ++ ++/* ++ * The below functions exist for backwards compatibility. ++ * No new uses should be written. ++ */ ++/* ++ * Get the health of the interrupt subsystem. ++ * @gasket_dev: The Gasket device struct. ++ * ++ * Returns DEAD if not set up, LAMED if initialization failed, and ALIVE ++ * otherwise. ++ */ ++ ++int gasket_interrupt_system_status(struct gasket_dev *gasket_dev); ++ ++#endif +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_ioctl.c b/drivers/staging/gasket-driver/gasket_ioctl.c +--- a/drivers/staging/gasket-driver/gasket_ioctl.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_ioctl.c 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,470 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright (C) 2018 Google, Inc. */ ++#include "gasket.h" ++#include "gasket_ioctl.h" ++#include "gasket_constants.h" ++#include "gasket_core.h" ++#include "gasket_interrupt.h" ++#include "gasket_page_table.h" ++#include ++#include ++#include ++#include ++ ++#ifdef GASKET_KERNEL_TRACE_SUPPORT ++#define CREATE_TRACE_POINTS ++#include ++#else ++#define trace_gasket_ioctl_entry(x, ...) ++#define trace_gasket_ioctl_exit(x) ++#define trace_gasket_ioctl_integer_data(x) ++#define trace_gasket_ioctl_eventfd_data(x, ...) ++#define trace_gasket_ioctl_page_table_data(x, ...) ++#define trace_gasket_ioctl_page_table_flags_data(x, ...) ++#define trace_gasket_ioctl_config_coherent_allocator(x, ...) ++#endif ++ ++/* Associate an eventfd with an interrupt. */ ++static int gasket_set_event_fd(struct gasket_dev *gasket_dev, ++ struct gasket_interrupt_eventfd __user *argp) ++{ ++ struct gasket_interrupt_eventfd die; ++ ++ if (copy_from_user(&die, argp, sizeof(struct gasket_interrupt_eventfd))) ++ return -EFAULT; ++ ++ trace_gasket_ioctl_eventfd_data(die.interrupt, die.event_fd); ++ ++ return gasket_interrupt_set_eventfd( ++ gasket_dev->interrupt_data, die.interrupt, die.event_fd); ++} ++ ++/* Read the size of the page table. */ ++static int gasket_read_page_table_size( ++ struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl __user *argp) ++{ ++ int ret = 0; ++ struct gasket_page_table_ioctl ibuf; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl))) ++ return -EFAULT; ++ ++ if (ibuf.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ ++ ibuf.size = gasket_page_table_num_entries( ++ gasket_dev->page_table[ibuf.page_table_index]); ++ ++ trace_gasket_ioctl_page_table_data( ++ ibuf.page_table_index, ibuf.size, ibuf.host_address, ++ ibuf.device_address); ++ ++ if (copy_to_user(argp, &ibuf, sizeof(ibuf))) ++ return -EFAULT; ++ ++ return ret; ++} ++ ++/* Read the size of the simple page table. */ ++static int gasket_read_simple_page_table_size( ++ struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl __user *argp) ++{ ++ int ret = 0; ++ struct gasket_page_table_ioctl ibuf; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl))) ++ return -EFAULT; ++ ++ if (ibuf.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ ++ ibuf.size = ++ gasket_page_table_num_simple_entries(gasket_dev->page_table[ibuf.page_table_index]); ++ ++ trace_gasket_ioctl_page_table_data(ibuf.page_table_index, ibuf.size, ++ ibuf.host_address, ++ ibuf.device_address); ++ ++ if (copy_to_user(argp, &ibuf, sizeof(ibuf))) ++ return -EFAULT; ++ ++ return ret; ++} ++ ++/* Set the boundary between the simple and extended page tables. */ ++static int gasket_partition_page_table( ++ struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl __user *argp) ++{ ++ int ret; ++ struct gasket_page_table_ioctl ibuf; ++ uint max_page_table_size; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl))) ++ return -EFAULT; ++ ++ trace_gasket_ioctl_page_table_data( ++ ibuf.page_table_index, ibuf.size, ibuf.host_address, ++ ibuf.device_address); ++ ++ if (ibuf.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ max_page_table_size = gasket_page_table_max_size( ++ gasket_dev->page_table[ibuf.page_table_index]); ++ ++ if (ibuf.size > max_page_table_size) { ++ dev_dbg(gasket_dev->dev, ++ "Partition request 0x%llx too large, max is 0x%x\n", ++ ibuf.size, max_page_table_size); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&gasket_dev->mutex); ++ ++ ret = gasket_page_table_partition( ++ gasket_dev->page_table[ibuf.page_table_index], ibuf.size); ++ mutex_unlock(&gasket_dev->mutex); ++ ++ return ret; ++} ++ ++/* Map a userspace buffer to a device virtual address. */ ++static int gasket_map_buffers_common(struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl_flags ++ *pibuf) ++{ ++ if (pibuf->base.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ ++ if (gasket_page_table_are_addrs_bad(gasket_dev->page_table[pibuf->base.page_table_index], ++ pibuf->base.host_address, ++ pibuf->base.device_address, ++ pibuf->base.size)) ++ return -EINVAL; ++ ++ return gasket_page_table_map(gasket_dev->page_table[pibuf->base.page_table_index], ++ pibuf->base.host_address, ++ pibuf->base.device_address, ++ pibuf->base.size / PAGE_SIZE, ++ pibuf->flags); ++} ++ ++static int gasket_map_buffers(struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl __user *argp) ++{ ++ struct gasket_page_table_ioctl_flags ibuf; ++ ++ if (copy_from_user(&ibuf.base, argp, sizeof(struct gasket_page_table_ioctl))) ++ return -EFAULT; ++ ++ ibuf.flags = 0; ++ ++ trace_gasket_ioctl_page_table_data(ibuf.base.page_table_index, ++ ibuf.base.size, ++ ibuf.base.host_address, ++ ibuf.base.device_address); ++ ++ return gasket_map_buffers_common(gasket_dev, &ibuf); ++} ++ ++static int gasket_map_buffers_flags(struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl_flags __user *argp) ++{ ++ struct gasket_page_table_ioctl_flags ibuf; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl_flags))) ++ return -EFAULT; ++ ++ trace_gasket_ioctl_page_table_flags_data(ibuf.base.page_table_index, ++ ibuf.base.size, ++ ibuf.base.host_address, ++ ibuf.base.device_address, ++ ibuf.flags); ++ ++ return gasket_map_buffers_common(gasket_dev, &ibuf); ++} ++ ++/* Unmap a userspace buffer from a device virtual address. */ ++static int gasket_unmap_buffers(struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl __user *argp) ++{ ++ struct gasket_page_table_ioctl ibuf; ++ ++ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl))) ++ return -EFAULT; ++ ++ trace_gasket_ioctl_page_table_data(ibuf.page_table_index, ibuf.size, ++ ibuf.host_address, ++ ibuf.device_address); ++ ++ if (ibuf.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ ++ if (gasket_page_table_is_dev_addr_bad(gasket_dev->page_table[ibuf.page_table_index], ++ ibuf.device_address, ibuf.size)) ++ return -EINVAL; ++ ++ gasket_page_table_unmap(gasket_dev->page_table[ibuf.page_table_index], ++ ibuf.device_address, ibuf.size / PAGE_SIZE); ++ ++ return 0; ++} ++ ++/* Map/unmap dma-buf to/from a device virtual address. */ ++static int gasket_map_dmabuf(struct gasket_dev *gasket_dev, ++ struct gasket_page_table_ioctl_dmabuf __user *argp) ++{ ++ struct gasket_page_table_ioctl_dmabuf dbuf; ++ struct gasket_page_table *pg_tbl; ++ ++ if (copy_from_user(&dbuf, argp, sizeof(dbuf))) ++ return -EFAULT; ++ ++ if (dbuf.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ ++ pg_tbl = gasket_dev->page_table[dbuf.page_table_index]; ++ if (gasket_page_table_is_dev_addr_bad(pg_tbl, ++ dbuf.device_address, ++ dbuf.num_pages * PAGE_SIZE)) ++ return -EINVAL; ++ ++ if (dbuf.map) ++ return gasket_page_table_map_dmabuf(pg_tbl, ++ dbuf.dmabuf_fd, ++ dbuf.device_address, ++ dbuf.num_pages, ++ dbuf.flags); ++ else ++ return gasket_page_table_unmap_dmabuf(pg_tbl, ++ dbuf.dmabuf_fd, ++ dbuf.device_address, ++ dbuf.num_pages); ++} ++ ++/* ++ * Reserve structures for coherent allocation, and allocate or free the ++ * corresponding memory. ++ */ ++static int gasket_config_coherent_allocator( ++ struct gasket_dev *gasket_dev, ++ struct gasket_coherent_alloc_config_ioctl __user *argp) ++{ ++ int ret; ++ struct gasket_coherent_alloc_config_ioctl ibuf; ++ dma_addr_t dma_address; ++ ++ if (copy_from_user(&ibuf, argp, ++ sizeof(struct gasket_coherent_alloc_config_ioctl))) ++ return -EFAULT; ++ ++ trace_gasket_ioctl_config_coherent_allocator(ibuf.enable, ibuf.size, ++ ibuf.dma_address); ++ ++ if (ibuf.page_table_index >= gasket_dev->num_page_tables) ++ return -EFAULT; ++ ++ if (ibuf.size > PAGE_SIZE * MAX_NUM_COHERENT_PAGES) ++ return -ENOMEM; ++ ++ if (ibuf.enable == 0) { ++ dma_address = ibuf.dma_address; ++ ret = gasket_free_coherent_memory(gasket_dev, ibuf.size, ++ dma_address, ++ ibuf.page_table_index); ++ } else { ++ ret = gasket_alloc_coherent_memory(gasket_dev, ibuf.size, ++ &dma_address, ++ ibuf.page_table_index); ++ } ++ if (ret) ++ return ret; ++ ++ if (ibuf.enable != 0) ++ ibuf.dma_address = dma_address; ++ ++ if (copy_to_user(argp, &ibuf, sizeof(ibuf))) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++/* Check permissions for Gasket ioctls. */ ++static bool gasket_ioctl_check_permissions(struct file *filp, uint cmd) ++{ ++ bool alive; ++ bool read, write; ++ struct gasket_dev *gasket_dev = (struct gasket_dev *)filp->private_data; ++ ++ alive = (gasket_dev->status == GASKET_STATUS_ALIVE); ++ if (!alive) ++ dev_dbg(gasket_dev->dev, "%s alive %d status %d\n", ++ __func__, alive, gasket_dev->status); ++ ++ read = !!(filp->f_mode & FMODE_READ); ++ write = !!(filp->f_mode & FMODE_WRITE); ++ ++ switch (cmd) { ++ case GASKET_IOCTL_RESET: ++ case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS: ++ return write; ++ ++ case GASKET_IOCTL_PAGE_TABLE_SIZE: ++ case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE: ++ case GASKET_IOCTL_NUMBER_PAGE_TABLES: ++ return read; ++ ++ case GASKET_IOCTL_PARTITION_PAGE_TABLE: ++ case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR: ++ return alive && write; ++ ++ case GASKET_IOCTL_MAP_BUFFER: ++ case GASKET_IOCTL_MAP_BUFFER_FLAGS: ++ case GASKET_IOCTL_UNMAP_BUFFER: ++ case GASKET_IOCTL_MAP_DMABUF: ++ return alive && write; ++ ++ case GASKET_IOCTL_CLEAR_EVENTFD: ++ case GASKET_IOCTL_SET_EVENTFD: ++ return alive && write; ++ } ++ ++ return false; /* unknown permissions */ ++} ++ ++/* ++ * standard ioctl dispatch function. ++ * @filp: File structure pointer describing this node usage session. ++ * @cmd: ioctl number to handle. ++ * @argp: ioctl-specific data pointer. ++ * ++ * Standard ioctl dispatcher; forwards operations to individual handlers. ++ */ ++long gasket_handle_ioctl(struct file *filp, uint cmd, void __user *argp) ++{ ++ struct gasket_dev *gasket_dev; ++ unsigned long arg = (unsigned long)argp; ++ gasket_ioctl_permissions_cb_t ioctl_permissions_cb; ++ int retval; ++ ++ gasket_dev = (struct gasket_dev *)filp->private_data; ++ trace_gasket_ioctl_entry(gasket_dev->dev_info.name, cmd); ++ ++ ioctl_permissions_cb = gasket_get_ioctl_permissions_cb(gasket_dev); ++ if (ioctl_permissions_cb) { ++ retval = ioctl_permissions_cb(filp, cmd, argp); ++ if (retval < 0) { ++ trace_gasket_ioctl_exit(retval); ++ return retval; ++ } else if (retval == 0) { ++ trace_gasket_ioctl_exit(-EPERM); ++ return -EPERM; ++ } ++ } else if (!gasket_ioctl_check_permissions(filp, cmd)) { ++ trace_gasket_ioctl_exit(-EPERM); ++ dev_dbg(gasket_dev->dev, "ioctl cmd=%x noperm\n", cmd); ++ return -EPERM; ++ } ++ ++ /* Tracing happens in this switch statement for all ioctls with ++ * an integer argrument, but ioctls with a struct argument ++ * that needs copying and decoding, that tracing is done within ++ * the handler call. ++ */ ++ switch (cmd) { ++ case GASKET_IOCTL_RESET: ++ retval = gasket_reset(gasket_dev); ++ break; ++ case GASKET_IOCTL_SET_EVENTFD: ++ retval = gasket_set_event_fd(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_CLEAR_EVENTFD: ++ trace_gasket_ioctl_integer_data(arg); ++ retval = ++ gasket_interrupt_clear_eventfd(gasket_dev->interrupt_data, ++ (int)arg); ++ break; ++ case GASKET_IOCTL_PARTITION_PAGE_TABLE: ++ trace_gasket_ioctl_integer_data(arg); ++ retval = gasket_partition_page_table(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_NUMBER_PAGE_TABLES: ++ trace_gasket_ioctl_integer_data(gasket_dev->num_page_tables); ++ if (copy_to_user(argp, &gasket_dev->num_page_tables, ++ sizeof(uint64_t))) ++ retval = -EFAULT; ++ else ++ retval = 0; ++ break; ++ case GASKET_IOCTL_PAGE_TABLE_SIZE: ++ retval = gasket_read_page_table_size(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE: ++ retval = gasket_read_simple_page_table_size(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_MAP_BUFFER: ++ retval = gasket_map_buffers(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_MAP_BUFFER_FLAGS: ++ retval = gasket_map_buffers_flags(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR: ++ retval = gasket_config_coherent_allocator(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_UNMAP_BUFFER: ++ retval = gasket_unmap_buffers(gasket_dev, argp); ++ break; ++ case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS: ++ /* Clear interrupt counts doesn't take an arg, so use 0. */ ++ trace_gasket_ioctl_integer_data(0); ++ retval = gasket_interrupt_reset_counts(gasket_dev); ++ break; ++ case GASKET_IOCTL_MAP_DMABUF: ++ retval = gasket_map_dmabuf(gasket_dev, argp); ++ break; ++ default: ++ /* If we don't understand the ioctl, the best we can do is trace ++ * the arg. ++ */ ++ trace_gasket_ioctl_integer_data(arg); ++ dev_dbg(gasket_dev->dev, ++ "Unknown ioctl cmd=0x%x not caught by " ++ "gasket_is_supported_ioctl\n", ++ cmd); ++ retval = -EINVAL; ++ break; ++ } ++ ++ trace_gasket_ioctl_exit(retval); ++ return retval; ++} ++ ++/* ++ * Determines if an ioctl is part of the standard Gasket framework. ++ * @cmd: The ioctl number to handle. ++ * ++ * Returns 1 if the ioctl is supported and 0 otherwise. ++ */ ++long gasket_is_supported_ioctl(uint cmd) ++{ ++ switch (cmd) { ++ case GASKET_IOCTL_RESET: ++ case GASKET_IOCTL_SET_EVENTFD: ++ case GASKET_IOCTL_CLEAR_EVENTFD: ++ case GASKET_IOCTL_PARTITION_PAGE_TABLE: ++ case GASKET_IOCTL_NUMBER_PAGE_TABLES: ++ case GASKET_IOCTL_PAGE_TABLE_SIZE: ++ case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE: ++ case GASKET_IOCTL_MAP_BUFFER: ++ case GASKET_IOCTL_MAP_BUFFER_FLAGS: ++ case GASKET_IOCTL_UNMAP_BUFFER: ++ case GASKET_IOCTL_MAP_DMABUF: ++ case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS: ++ case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR: ++ return 1; ++ default: ++ return 0; ++ } ++} +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_ioctl.h b/drivers/staging/gasket-driver/gasket_ioctl.h +--- a/drivers/staging/gasket-driver/gasket_ioctl.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_ioctl.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,28 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright (C) 2018 Google, Inc. */ ++#ifndef __GASKET_IOCTL_H__ ++#define __GASKET_IOCTL_H__ ++ ++#include "gasket_core.h" ++ ++#include ++ ++/* ++ * Handle Gasket common ioctls. ++ * @filp: Pointer to the ioctl's file. ++ * @cmd: Ioctl command. ++ * @arg: Ioctl argument pointer. ++ * ++ * Returns 0 on success and nonzero on failure. ++ */ ++long gasket_handle_ioctl(struct file *filp, uint cmd, void __user *argp); ++ ++/* ++ * Determines if an ioctl is part of the standard Gasket framework. ++ * @cmd: The ioctl number to handle. ++ * ++ * Returns 1 if the ioctl is supported and 0 otherwise. ++ */ ++long gasket_is_supported_ioctl(uint cmd); ++ ++#endif +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_page_table.c b/drivers/staging/gasket-driver/gasket_page_table.c +--- a/drivers/staging/gasket-driver/gasket_page_table.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_page_table.c 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,1597 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Implementation of Gasket page table support. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++ ++/* ++ * Implementation of Gasket page table support. ++ * ++ * This file assumes 4kB pages throughout; can be factored out when necessary. ++ * ++ * There is a configurable number of page table entries, as well as a ++ * configurable bit index for the extended address flag. Both of these are ++ * specified in gasket_page_table_init through the page_table_config parameter. ++ * ++ * The following example assumes: ++ * page_table_config->total_entries = 8192 ++ * page_table_config->extended_bit = 63 ++ * ++ * Address format: ++ * Simple addresses - those whose containing pages are directly placed in the ++ * device's address translation registers - are laid out as: ++ * [ 63 - 25: 0 | 24 - 12: page index | 11 - 0: page offset ] ++ * page index: The index of the containing page in the device's address ++ * translation registers. ++ * page offset: The index of the address into the containing page. ++ * ++ * Extended address - those whose containing pages are contained in a second- ++ * level page table whose address is present in the device's address translation ++ * registers - are laid out as: ++ * [ 63: flag | 62 - 34: 0 | 33 - 21: dev/level 0 index | ++ * 20 - 12: host/level 1 index | 11 - 0: page offset ] ++ * flag: Marker indicating that this is an extended address. Always 1. ++ * dev index: The index of the first-level page in the device's extended ++ * address translation registers. ++ * host index: The index of the containing page in the [host-resident] second- ++ * level page table. ++ * page offset: The index of the address into the containing [second-level] ++ * page. ++ */ ++#include "gasket_page_table.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#if __has_include() ++MODULE_IMPORT_NS(DMA_BUF); ++#endif ++ ++#include "gasket_constants.h" ++#include "gasket_core.h" ++ ++/* Constants & utility macros */ ++/* The number of pages that can be mapped into each second-level page table. */ ++#define GASKET_PAGES_PER_SUBTABLE 512 ++ ++/* The starting position of the page index in a simple virtual address. */ ++#define GASKET_SIMPLE_PAGE_SHIFT 12 ++ ++/* Flag indicating that a [device] slot is valid for use. */ ++#define GASKET_VALID_SLOT_FLAG 1 ++ ++/* ++ * The starting position of the level 0 page index (i.e., the entry in the ++ * device's extended address registers) in an extended address. ++ * Also can be thought of as (log2(PAGE_SIZE) + log2(PAGES_PER_SUBTABLE)), ++ * or (12 + 9). ++ */ ++#define GASKET_EXTENDED_LVL0_SHIFT 21 ++ ++/* ++ * Number of first level pages that Gasket chips support. Equivalent to ++ * log2(NUM_LVL0_PAGE_TABLES) ++ * ++ * At a maximum, allowing for a 34 bits address space (or 16GB) ++ * = GASKET_EXTENDED_LVL0_WIDTH + (log2(PAGE_SIZE) + log2(PAGES_PER_SUBTABLE) ++ * or, = 13 + 9 + 12 ++ */ ++#define GASKET_EXTENDED_LVL0_WIDTH 13 ++ ++/* ++ * The starting position of the level 1 page index (i.e., the entry in the ++ * host second-level/sub- table) in an extended address. ++ */ ++#define GASKET_EXTENDED_LVL1_SHIFT 12 ++ ++/* ++ * Utilities for accessing flags bitfields. ++ */ ++#define MASK(field) (((1u << field##_WIDTH) - 1) << field##_SHIFT) ++#define GET(field, flags) (((flags) & MASK(field)) >> field##_SHIFT) ++#define SET(field, flags, val) (((flags) & ~MASK(field)) | ((val) << field##_SHIFT)) ++ ++#define FLAGS_STATUS_SHIFT 0 ++#define FLAGS_STATUS_WIDTH 1 ++ ++#define FLAGS_DMA_DIRECTION_SHIFT 1 ++#define FLAGS_DMA_DIRECTION_WIDTH 2 ++ ++/* Type declarations */ ++/* Valid states for a struct gasket_page_table_entry. */ ++enum pte_status { ++ PTE_FREE, ++ PTE_INUSE, ++}; ++ ++/* ++ * Mapping metadata for a single page. ++ * ++ * In this file, host-side page table entries are referred to as that (or PTEs). ++ * Where device vs. host entries are differentiated, device-side or -visible ++ * entries are called "slots". A slot may be either an entry in the device's ++ * address translation table registers or an entry in a second-level page ++ * table ("subtable"). ++ * ++ * The full data in this structure is visible on the host [of course]. Only ++ * the address contained in dma_addr is communicated to the device; that points ++ * to the actual page mapped and described by this structure. ++ */ ++struct gasket_page_table_entry { ++ /* ++ * Internal structure matches gasket_page_table_ioctl_flags.flags. ++ * NOTE: All fields should have a default value of 0. This ensures that ++ * the kernel will be backwards compatible with old drivers. ++ */ ++ u32 flags; ++ ++ /* ++ * Index for alignment into host vaddrs. ++ * When a user specifies a host address for a mapping, that address may ++ * not be page-aligned. Offset is the index into the containing page of ++ * the host address (i.e., host_vaddr & (PAGE_SIZE - 1)). ++ * This is necessary for translating between user-specified addresses ++ * and page-aligned addresses. ++ */ ++ int offset; ++ ++ /* Address of the page in DMA space. */ ++ dma_addr_t dma_addr; ++ ++ /* Linux page descriptor for the page described by this structure. */ ++ struct page *page; ++ ++ /* ++ * If this is an extended and first-level entry, sublevel points ++ * to the second-level entries underneath this entry. ++ */ ++ struct gasket_page_table_entry *sublevel; ++}; ++ ++/* ++ * Maintains virtual to physical address mapping for a coherent page that is ++ * allocated by this module for a given device. ++ * Note that coherent pages mappings virt mapping cannot be tracked by the ++ * Linux kernel, and coherent pages don't have a struct page associated, ++ * hence Linux kernel cannot perform a get_user_page_xx() on a phys address ++ * that was allocated coherent. ++ * This structure trivially implements this mechanism. ++ */ ++struct gasket_coherent_page_entry { ++ /* Phys address, dma'able by the owner device */ ++ dma_addr_t paddr; ++ ++ /* Kernel virtual address */ ++ u64 user_virt; ++ ++ /* User virtual address that was mapped by the mmap kernel subsystem */ ++ dma_addr_t kernel_virt; ++ ++ /* ++ * Whether this page has been mapped into a user land process virtual ++ * space ++ */ ++ u32 in_use; ++}; ++ ++/* Storage for dmabuf mapping information. */ ++struct gasket_dmabuf_mapping { ++ struct dma_buf *dmabuf; ++ struct dma_buf_attachment *attachment; ++ struct sg_table *sgt; ++ enum dma_data_direction direction; ++ struct list_head list; ++}; ++ ++/* ++ * [Host-side] page table descriptor. ++ * ++ * This structure tracks the metadata necessary to manage both simple and ++ * extended page tables. ++ */ ++struct gasket_page_table { ++ /* The config used to create this page table. */ ++ struct gasket_page_table_config config; ++ ++ /* The number of simple (single-level) entries in the page table. */ ++ uint num_simple_entries; ++ ++ /* The number of extended (two-level) entries in the page table. */ ++ uint num_extended_entries; ++ ++ /* Array of [host-side] page table entries. */ ++ struct gasket_page_table_entry *entries; ++ ++ /* Number of actively mapped kernel pages in this table. */ ++ uint num_active_pages; ++ ++ /* Device register: base of/first slot in the page table. */ ++ u64 __iomem *base_slot; ++ ++ /* Device register: holds the offset indicating the start of the ++ * extended address region of the device's address translation table. ++ */ ++ u64 __iomem *extended_offset_reg; ++ ++ /* Device structure for the underlying device. Only used for logging. */ ++ struct device *device; ++ ++ /* PCI system descriptor for the underlying device. */ ++ struct pci_dev *pci_dev; ++ ++ /* Location of the extended address bit for this Gasket device. */ ++ u64 extended_flag; ++ ++ /* Mutex to protect page table internals. */ ++ struct mutex mutex; ++ ++ /* Number of coherent pages accessible thru by this page table */ ++ int num_coherent_pages; ++ ++ /* ++ * List of coherent memory (physical) allocated for a device. ++ * ++ * This structure also remembers the user virtual mapping, this is ++ * hacky, but we need to do this because the kernel doesn't keep track ++ * of the user coherent pages (pfn pages), and virt to coherent page ++ * mapping. ++ * TODO: use find_vma() APIs to convert host address to vm_area, to ++ * dma_addr_t instead of storing user virtu address in ++ * gasket_coherent_page_entry ++ * ++ * Note that the user virtual mapping is created by the driver, in ++ * gasket_mmap function, so user_virt belongs in the driver anyhow. ++ */ ++ struct gasket_coherent_page_entry *coherent_pages; ++ ++ /* List of dmabufs currently attached and mapped. */ ++ struct list_head dmabufs; ++}; ++ ++/* See gasket_page_table.h for description. */ ++int gasket_page_table_init(struct gasket_page_table **ppg_tbl, ++ const struct gasket_bar_data *bar_data, ++ const struct gasket_page_table_config *page_table_config, ++ struct device *device, struct pci_dev *pci_dev) ++{ ++ ulong bytes; ++ struct gasket_page_table *pg_tbl; ++ ulong total_entries = page_table_config->total_entries; ++ ++ /* ++ * TODO: Verify config->total_entries against value read from the ++ * hardware register that contains the page table size. ++ */ ++ if (total_entries == ULONG_MAX) { ++ dev_dbg(device, "Error reading page table size. " ++ "Initializing page table with size 0\n"); ++ total_entries = 0; ++ } ++ ++ dev_dbg(device, ++ "Attempting to initialize page table of size 0x%lx\n", ++ total_entries); ++ ++ dev_dbg(device, ++ "Table has base reg 0x%x, extended offset reg 0x%x\n", ++ page_table_config->base_reg, ++ page_table_config->extended_reg); ++ ++ *ppg_tbl = kzalloc(sizeof(**ppg_tbl), GFP_KERNEL); ++ if (!*ppg_tbl) { ++ dev_dbg(device, "No memory for page table\n"); ++ return -ENOMEM; ++ } ++ ++ pg_tbl = *ppg_tbl; ++ bytes = total_entries * sizeof(struct gasket_page_table_entry); ++ if (bytes != 0) { ++ pg_tbl->entries = vzalloc(bytes); ++ if (!pg_tbl->entries) { ++ dev_dbg(device, ++ "No memory for address translation metadata\n"); ++ kfree(pg_tbl); ++ *ppg_tbl = NULL; ++ return -ENOMEM; ++ } ++ } ++ ++ mutex_init(&pg_tbl->mutex); ++ memcpy(&pg_tbl->config, page_table_config, sizeof(*page_table_config)); ++ if (pg_tbl->config.mode == GASKET_PAGE_TABLE_MODE_NORMAL || ++ pg_tbl->config.mode == GASKET_PAGE_TABLE_MODE_SIMPLE) { ++ pg_tbl->num_simple_entries = total_entries; ++ pg_tbl->num_extended_entries = 0; ++ pg_tbl->extended_flag = 1ull << page_table_config->extended_bit; ++ } else { ++ pg_tbl->num_simple_entries = 0; ++ pg_tbl->num_extended_entries = total_entries; ++ pg_tbl->extended_flag = 0; ++ } ++ pg_tbl->num_active_pages = 0; ++ pg_tbl->base_slot = ++ (u64 __iomem *)&bar_data->virt_base[page_table_config->base_reg]; ++ pg_tbl->extended_offset_reg = ++ (u64 __iomem *)&bar_data->virt_base[page_table_config->extended_reg]; ++ pg_tbl->device = get_device(device); ++ pg_tbl->pci_dev = pci_dev; ++ INIT_LIST_HEAD(&pg_tbl->dmabufs); ++ ++ dev_dbg(device, "Page table initialized successfully\n"); ++ ++ return 0; ++} ++ ++/* ++ * Check if a range of PTEs is free. ++ * The page table mutex must be held by the caller. ++ */ ++static bool gasket_is_pte_range_free(struct gasket_page_table_entry *ptes, ++ uint num_entries) ++{ ++ int i; ++ ++ for (i = 0; i < num_entries; i++) { ++ if (GET(FLAGS_STATUS, ptes[i].flags) != PTE_FREE) ++ return false; ++ } ++ ++ return true; ++} ++ ++/* ++ * Free a second level page [sub]table. ++ * The page table mutex must be held before this call. ++ */ ++static void gasket_free_extended_subtable(struct gasket_page_table *pg_tbl, ++ struct gasket_page_table_entry *pte, ++ u64 __iomem *slot) ++{ ++ /* Release the page table from the driver */ ++ pte->flags = SET(FLAGS_STATUS, pte->flags, PTE_FREE); ++ ++ /* Release the page table from the device */ ++ writeq(0, slot); ++ ++ if (pte->dma_addr) ++ dma_unmap_page(pg_tbl->device, pte->dma_addr, PAGE_SIZE, ++ DMA_TO_DEVICE); ++ ++ vfree(pte->sublevel); ++ ++ if (pte->page) ++ free_page((ulong)page_address(pte->page)); ++ ++ memset(pte, 0, sizeof(struct gasket_page_table_entry)); ++} ++ ++/* ++ * Actually perform collection. ++ * The page table mutex must be held by the caller. ++ */ ++static void ++gasket_page_table_garbage_collect_nolock(struct gasket_page_table *pg_tbl) ++{ ++ struct gasket_page_table_entry *pte; ++ u64 __iomem *slot; ++ ++ /* XXX FIX ME XXX -- more efficient to keep a usage count */ ++ /* rather than scanning the second level page tables */ ++ ++ for (pte = pg_tbl->entries + pg_tbl->num_simple_entries, ++ slot = pg_tbl->base_slot + pg_tbl->num_simple_entries; ++ pte < pg_tbl->entries + pg_tbl->config.total_entries; ++ pte++, slot++) { ++ if (GET(FLAGS_STATUS, pte->flags) == PTE_INUSE) { ++ if (gasket_is_pte_range_free(pte->sublevel, ++ GASKET_PAGES_PER_SUBTABLE)) ++ gasket_free_extended_subtable(pg_tbl, pte, ++ slot); ++ } ++ } ++} ++ ++/* See gasket_page_table.h for description. */ ++void gasket_page_table_garbage_collect(struct gasket_page_table *pg_tbl) ++{ ++ mutex_lock(&pg_tbl->mutex); ++ gasket_page_table_garbage_collect_nolock(pg_tbl); ++ mutex_unlock(&pg_tbl->mutex); ++} ++ ++/* See gasket_page_table.h for description. */ ++void gasket_page_table_cleanup(struct gasket_page_table *pg_tbl) ++{ ++ /* Deallocate free second-level tables. */ ++ gasket_page_table_garbage_collect(pg_tbl); ++ ++ /* TODO: Check that all PTEs have been freed? */ ++ ++ vfree(pg_tbl->entries); ++ pg_tbl->entries = NULL; ++ ++ put_device(pg_tbl->device); ++ kfree(pg_tbl); ++} ++ ++/* See gasket_page_table.h for description. */ ++int gasket_page_table_partition(struct gasket_page_table *pg_tbl, ++ uint num_simple_entries) ++{ ++ int i, start; ++ ++ mutex_lock(&pg_tbl->mutex); ++ if (num_simple_entries > pg_tbl->config.total_entries) { ++ mutex_unlock(&pg_tbl->mutex); ++ return -EINVAL; ++ } ++ ++ gasket_page_table_garbage_collect_nolock(pg_tbl); ++ ++ start = min(pg_tbl->num_simple_entries, num_simple_entries); ++ ++ for (i = start; i < pg_tbl->config.total_entries; i++) { ++ if (GET(FLAGS_STATUS, pg_tbl->entries[i].flags) != PTE_FREE) { ++ dev_err(pg_tbl->device, "entry %d is not free\n", i); ++ mutex_unlock(&pg_tbl->mutex); ++ return -EBUSY; ++ } ++ } ++ ++ pg_tbl->num_simple_entries = num_simple_entries; ++ pg_tbl->num_extended_entries = ++ pg_tbl->config.total_entries - num_simple_entries; ++ writeq(num_simple_entries, pg_tbl->extended_offset_reg); ++ ++ mutex_unlock(&pg_tbl->mutex); ++ return 0; ++} ++EXPORT_SYMBOL(gasket_page_table_partition); ++ ++/* ++ * Return whether a host buffer was mapped as coherent memory. ++ * ++ * A Gasket page_table currently support one contiguous dma range, mapped to one ++ * contiguous virtual memory range. Check if the host_addr is within that range. ++ */ ++static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr) ++{ ++ u64 min, max; ++ ++ /* whether the host address is within user virt range */ ++ if (!pg_tbl->coherent_pages) ++ return 0; ++ ++ min = (u64)pg_tbl->coherent_pages[0].user_virt; ++ max = min + PAGE_SIZE * pg_tbl->num_coherent_pages; ++ ++ return min <= host_addr && host_addr < max; ++} ++ ++/* Safely return a page to the OS. */ ++static bool gasket_release_page(struct page *page) ++{ ++ if (!page) ++ return false; ++ ++ if (!PageReserved(page)) ++ SetPageDirty(page); ++ put_page(page); ++ ++ return true; ++} ++ ++/* ++ * Get and map last level page table buffers. ++ * ++ * slots is the location(s) to write device-mapped page address. If this is a ++ * simple mapping, these will be address translation registers. If this is ++ * an extended mapping, these will be within a second-level page table ++ * allocated by the host and so must have their __iomem attribute casted away. ++ */ ++static int gasket_perform_mapping(struct gasket_page_table *pg_tbl, ++ struct gasket_page_table_entry *ptes, ++ u64 __iomem *slots, ++ struct sg_page_iter *sg_iter, ++ ulong host_addr, ++ uint num_pages, u32 flags, ++ int is_simple_mapping) ++{ ++ int ret; ++ ulong offset; ++ struct page *page; ++ dma_addr_t dma_addr; ++ ulong page_addr; ++ int i; ++ enum dma_data_direction direction; ++ ++ /* Must have a virtual host address or a sg iterator, but not both. */ ++ if (!((uintptr_t)host_addr ^ (uintptr_t)sg_iter)) { ++ dev_err(pg_tbl->device, "need sg_iter or host_addr\n"); ++ return -EINVAL; ++ } ++ ++ direction = GET(FLAGS_DMA_DIRECTION, flags); ++ if (direction == DMA_NONE) { ++ dev_err(pg_tbl->device, "invalid DMA direction flags=0x%lx\n", ++ (unsigned long)flags); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < num_pages; i++) { ++ page_addr = host_addr + i * PAGE_SIZE; ++ offset = page_addr & (PAGE_SIZE - 1); ++ dev_dbg(pg_tbl->device, "%s i %d\n", __func__, i); ++ if (sg_iter) { ++ if (!__sg_page_iter_next(sg_iter)) ++ return -EINVAL; ++ ++ /* Page already mapped for DMA. */ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 1, 0) ++ ptes[i].dma_addr = sg_page_iter_dma_address(sg_iter); ++#else ++ ptes[i].dma_addr = sg_page_iter_dma_address( ++ container_of(sg_iter, struct sg_dma_page_iter, base)); ++#endif ++ ptes[i].page = NULL; ++ offset = 0; ++ } else if (is_coherent(pg_tbl, host_addr)) { ++ u64 off = ++ (u64)host_addr - ++ (u64)pg_tbl->coherent_pages[0].user_virt; ++ ptes[i].page = NULL; ++ ptes[i].offset = offset; ++ ptes[i].dma_addr = pg_tbl->coherent_pages[0].paddr + ++ off + i * PAGE_SIZE; ++ } else { ++ ret = get_user_pages_fast(page_addr - offset, 1, ++ direction != DMA_TO_DEVICE, ++ &page); ++ ++ if (ret <= 0) { ++ dev_err(pg_tbl->device, ++ "get user pages failed for addr=0x%lx, " ++ "offset=0x%lx [ret=%d]\n", ++ page_addr, offset, ret); ++ return ret ? ret : -ENOMEM; ++ } ++ ++pg_tbl->num_active_pages; ++ ++ ptes[i].page = page; ++ ptes[i].offset = offset; ++ ++ /* Map the page into DMA space. */ ++ ptes[i].dma_addr = dma_map_page(pg_tbl->device, page, 0, PAGE_SIZE, ++ GET(FLAGS_DMA_DIRECTION, flags)); ++ dev_dbg(pg_tbl->device, ++ "%s i %d pte %p pfn %p -> mapped %llx\n", ++ __func__, i, &ptes[i], ++ (void *)page_to_pfn(page), ++ (unsigned long long)ptes[i].dma_addr); ++ ++ if (dma_mapping_error(pg_tbl->device, ++ ptes[i].dma_addr)) { ++ dev_dbg(pg_tbl->device, ++ "%s i %d -> fail to map page %llx " ++ "[pfn %p phys %p]\n", ++ __func__, i, ++ (unsigned long long)ptes[i].dma_addr, ++ (void *)page_to_pfn(page), ++ (void *)page_to_phys(page)); ++ ++ /* clean up */ ++ if (gasket_release_page(ptes[i].page)) ++ --pg_tbl->num_active_pages; ++ ++ memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry)); ++ return -EINVAL; ++ } ++ } ++ ++ /* Make the DMA-space address available to the device. */ ++ dma_addr = (ptes[i].dma_addr + offset) | GASKET_VALID_SLOT_FLAG; ++ ++ if (is_simple_mapping) ++ writeq(dma_addr, &slots[i]); ++ else ++ ((u64 __force *)slots)[i] = dma_addr; ++ ++ /* Set PTE flags equal to flags param with STATUS=PTE_INUSE. */ ++ ptes[i].flags = SET(FLAGS_STATUS, flags, PTE_INUSE); ++ } ++ return 0; ++} ++ ++/* ++ * Return the index of the page for the address in the simple table. ++ * Does not perform validity checking. ++ */ ++static int gasket_simple_page_idx(struct gasket_page_table *pg_tbl, ++ u64 dev_addr) ++{ ++ return (dev_addr >> GASKET_SIMPLE_PAGE_SHIFT) & ++ (pg_tbl->config.total_entries - 1); ++} ++ ++/* ++ * Return the level 0 page index for the given address. ++ * Does not perform validity checking. ++ */ ++static ulong gasket_extended_lvl0_page_idx(struct gasket_page_table *pg_tbl, ++ u64 dev_addr) ++{ ++ return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) & ++ (pg_tbl->config.total_entries - 1); ++} ++ ++/* ++ * Return the level 1 page index for the given address. ++ * Does not perform validity checking. ++ */ ++static ulong gasket_extended_lvl1_page_idx(struct gasket_page_table *pg_tbl, ++ u64 dev_addr) ++{ ++ return (dev_addr >> GASKET_EXTENDED_LVL1_SHIFT) & ++ (GASKET_PAGES_PER_SUBTABLE - 1); ++} ++ ++/* ++ * Allocate page table entries in a simple table. ++ * The page table mutex must be held by the caller. ++ */ ++static int gasket_alloc_simple_entries(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_pages) ++{ ++ if (!gasket_is_pte_range_free(pg_tbl->entries + ++ gasket_simple_page_idx(pg_tbl, dev_addr), ++ num_pages)) ++ return -EBUSY; ++ ++ return 0; ++} ++ ++/* ++ * Unmap and release mapped pages. ++ * The page table mutex must be held by the caller. ++ */ ++static void gasket_perform_unmapping(struct gasket_page_table *pg_tbl, ++ struct gasket_page_table_entry *ptes, ++ u64 __iomem *slots, uint num_pages, ++ int is_simple_mapping) ++{ ++ int i; ++ /* ++ * For each page table entry and corresponding entry in the device's ++ * address translation table: ++ */ ++ for (i = 0; i < num_pages; i++) { ++ /* release the address from the device, */ ++ if (is_simple_mapping) ++ writeq(0, &slots[i]); ++ else ++ ((u64 __force *)slots)[i] = 0; ++ ++ /* release the address from the driver, */ ++ if (GET(FLAGS_STATUS, ptes[i].flags) == PTE_INUSE) { ++ if (ptes[i].page && ptes[i].dma_addr) { ++ dma_unmap_page(pg_tbl->device, ptes[i].dma_addr, PAGE_SIZE, ++ GET(FLAGS_DMA_DIRECTION, ptes[i].flags)); ++ } ++ if (gasket_release_page(ptes[i].page)) ++ --pg_tbl->num_active_pages; ++ } ++ ++ /* and clear the PTE. */ ++ memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry)); ++ } ++} ++ ++/* ++ * Unmap and release pages mapped to simple addresses. ++ * The page table mutex must be held by the caller. ++ */ ++static void gasket_unmap_simple_pages(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_pages) ++{ ++ uint slot = gasket_simple_page_idx(pg_tbl, dev_addr); ++ ++ gasket_perform_unmapping(pg_tbl, pg_tbl->entries + slot, ++ pg_tbl->base_slot + slot, num_pages, 1); ++} ++ ++/* ++ * Unmap and release buffers to extended addresses. ++ * The page table mutex must be held by the caller. ++ */ ++static void gasket_unmap_extended_pages(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_pages) ++{ ++ uint slot_idx, remain, len; ++ struct gasket_page_table_entry *pte; ++ u64 __iomem *slot_base; ++ ++ remain = num_pages; ++ slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr); ++ pte = pg_tbl->entries + pg_tbl->num_simple_entries + ++ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); ++ ++ while (remain > 0) { ++ /* TODO: Add check to ensure pte remains valid? */ ++ len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx); ++ ++ if (GET(FLAGS_STATUS, pte->flags) == PTE_INUSE) { ++ slot_base = (u64 __iomem *)(page_address(pte->page) + ++ pte->offset); ++ gasket_perform_unmapping(pg_tbl, ++ pte->sublevel + slot_idx, ++ slot_base + slot_idx, len, 0); ++ /* ++ * Extended page tables are in DRAM so they need to be ++ * synced each time they are updated. ++ */ ++ dma_sync_single_for_device(pg_tbl->device, ++ pte->dma_addr + slot_idx * sizeof(u64), ++ len * sizeof(u64), DMA_TO_DEVICE); ++ } ++ ++ remain -= len; ++ slot_idx = 0; ++ pte++; ++ } ++} ++ ++/* Evaluates to nonzero if the specified virtual address is simple. */ ++static inline bool gasket_addr_is_simple(struct gasket_page_table *pg_tbl, ++ u64 addr) ++{ ++ return !((addr) & (pg_tbl)->extended_flag); ++} ++ ++/* ++ * Convert (simple, page, offset) into a device address. ++ * Examples: ++ * Simple page 0, offset 32: ++ * Input (1, 0, 32), Output 0x20 ++ * Simple page 1000, offset 511: ++ * Input (1, 1000, 511), Output 0x3E81FF ++ * Extended page 0, offset 32: ++ * Input (0, 0, 32), Output 0x8000000020 ++ * Extended page 1000, offset 511: ++ * Input (0, 1000, 511), Output 0x8003E81FF ++ */ ++static u64 gasket_components_to_dev_address(struct gasket_page_table *pg_tbl, ++ int is_simple, uint page_index, ++ uint offset) ++{ ++ u64 dev_addr = (page_index << GASKET_SIMPLE_PAGE_SHIFT) | offset; ++ ++ return is_simple ? dev_addr : (pg_tbl->extended_flag | dev_addr); ++} ++ ++/* ++ * Validity checking for simple addresses. ++ * ++ * Verify that address translation commutes (from address to/from page + offset) ++ * and that the requested page range starts and ends within the set of ++ * currently-partitioned simple pages. ++ */ ++static bool gasket_is_simple_dev_addr_bad(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_pages) ++{ ++ ulong page_offset = dev_addr & (PAGE_SIZE - 1); ++ ulong page_index = ++ (dev_addr / PAGE_SIZE) & (pg_tbl->config.total_entries - 1); ++ ++ if (gasket_components_to_dev_address(pg_tbl, 1, page_index, ++ page_offset) != dev_addr) { ++ dev_err(pg_tbl->device, "address is invalid, 0x%llX\n", ++ dev_addr); ++ return true; ++ } ++ ++ if (page_index >= pg_tbl->num_simple_entries) { ++ dev_err(pg_tbl->device, ++ "starting slot at %lu is too large, max is < %u\n", ++ page_index, pg_tbl->num_simple_entries); ++ return true; ++ } ++ ++ if (page_index + num_pages > pg_tbl->num_simple_entries) { ++ dev_err(pg_tbl->device, ++ "ending slot at %lu is too large, max is <= %u\n", ++ page_index + num_pages, pg_tbl->num_simple_entries); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * Validity checking for extended addresses. ++ * ++ * Verify that address translation commutes (from address to/from page + ++ * offset) and that the requested page range starts and ends within the set of ++ * currently-partitioned extended pages. ++ */ ++static bool gasket_is_extended_dev_addr_bad(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_pages) ++{ ++ /* Starting byte index of dev_addr into the first mapped page */ ++ ulong page_offset = dev_addr & (PAGE_SIZE - 1); ++ ulong page_global_idx, page_lvl0_idx; ++ ulong num_lvl0_pages; ++ u64 addr; ++ ++ /* check if the device address is out of bound */ ++ addr = dev_addr & ~((pg_tbl)->extended_flag); ++ if (addr >> (GASKET_EXTENDED_LVL0_WIDTH + GASKET_EXTENDED_LVL0_SHIFT)) { ++ dev_err(pg_tbl->device, ++ "device address out of bounds: 0x%llx\n", dev_addr); ++ return true; ++ } ++ ++ /* Find the starting sub-page index in the space of all sub-pages. */ ++ page_global_idx = (dev_addr / PAGE_SIZE) & ++ (pg_tbl->config.total_entries * GASKET_PAGES_PER_SUBTABLE - 1); ++ ++ /* Find the starting level 0 index. */ ++ page_lvl0_idx = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); ++ ++ /* Get the count of affected level 0 pages. */ ++ num_lvl0_pages = (num_pages + GASKET_PAGES_PER_SUBTABLE - 1) / ++ GASKET_PAGES_PER_SUBTABLE; ++ ++ if (gasket_components_to_dev_address(pg_tbl, 0, page_global_idx, ++ page_offset) != dev_addr) { ++ dev_err(pg_tbl->device, "address is invalid: 0x%llx\n", ++ dev_addr); ++ return true; ++ } ++ ++ if (page_lvl0_idx >= pg_tbl->num_extended_entries) { ++ dev_err(pg_tbl->device, ++ "starting level 0 slot at %lu is too large, max is < " ++ "%u\n", page_lvl0_idx, pg_tbl->num_extended_entries); ++ return true; ++ } ++ ++ if (page_lvl0_idx + num_lvl0_pages > pg_tbl->num_extended_entries) { ++ dev_err(pg_tbl->device, ++ "ending level 0 slot at %lu is too large, max is <= %u\n", ++ page_lvl0_idx + num_lvl0_pages, ++ pg_tbl->num_extended_entries); ++ return true; ++ } ++ ++ return false; ++} ++ ++/* ++ * Non-locking entry to unmapping routines. ++ * The page table mutex must be held by the caller. ++ */ ++static void gasket_page_table_unmap_nolock(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_pages) ++{ ++ if (!num_pages) ++ return; ++ ++ if (gasket_addr_is_simple(pg_tbl, dev_addr)) ++ gasket_unmap_simple_pages(pg_tbl, dev_addr, num_pages); ++ else ++ gasket_unmap_extended_pages(pg_tbl, dev_addr, num_pages); ++} ++ ++/* ++ * Allocate and map pages to simple addresses. ++ * If there is an error, no pages are mapped. ++ */ ++static int gasket_map_simple_pages(struct gasket_page_table *pg_tbl, ++ struct sg_page_iter *sg_iter, ++ ulong host_addr, u64 dev_addr, ++ uint num_pages, u32 flags) ++{ ++ int ret; ++ uint slot_idx = gasket_simple_page_idx(pg_tbl, dev_addr); ++ ++ ret = gasket_alloc_simple_entries(pg_tbl, dev_addr, num_pages); ++ if (ret) { ++ dev_err(pg_tbl->device, ++ "page table slots %u (@ 0x%llx) to %u are not available\n", ++ slot_idx, (long long unsigned int)dev_addr, ++ slot_idx + num_pages - 1); ++ return ret; ++ } ++ ++ ret = gasket_perform_mapping(pg_tbl, pg_tbl->entries + slot_idx, ++ pg_tbl->base_slot + slot_idx, sg_iter, ++ host_addr, num_pages, flags, 1); ++ ++ if (ret) { ++ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages); ++ dev_err(pg_tbl->device, "gasket_perform_mapping %d\n", ret); ++ } ++ return ret; ++} ++ ++/* ++ * Allocate a second level page table. ++ * The page table mutex must be held by the caller. ++ */ ++static int gasket_alloc_extended_subtable(struct gasket_page_table *pg_tbl, ++ struct gasket_page_table_entry *pte, ++ u64 __iomem *slot) ++{ ++ ulong page_addr, subtable_bytes; ++ dma_addr_t dma_addr; ++ ++ /* XXX FIX ME XXX this is inefficient for non-4K page sizes */ ++ ++ /* GFP_DMA flag must be passed to architectures for which ++ * part of the memory range is not considered DMA'able. ++ * This seems to be the case for Juno board with 4.5.0 Linaro kernel ++ */ ++ page_addr = get_zeroed_page(GFP_KERNEL | GFP_DMA); ++ if (!page_addr) ++ return -ENOMEM; ++ pte->page = virt_to_page((void *)page_addr); ++ pte->offset = 0; ++ ++ subtable_bytes = sizeof(struct gasket_page_table_entry) * ++ GASKET_PAGES_PER_SUBTABLE; ++ pte->sublevel = vzalloc(subtable_bytes); ++ if (!pte->sublevel) { ++ free_page(page_addr); ++ memset(pte, 0, sizeof(struct gasket_page_table_entry)); ++ return -ENOMEM; ++ } ++ ++ /* Map the page into DMA space. */ ++ pte->dma_addr = dma_map_page(pg_tbl->device, pte->page, 0, PAGE_SIZE, ++ DMA_TO_DEVICE); ++ if (dma_mapping_error(pg_tbl->device, pte->dma_addr)) { ++ dev_dbg(pg_tbl->device, ++ "%s -> fail to map page %llx " ++ "[pfn %p phys %p]\n", ++ __func__, ++ (unsigned long long)pte->dma_addr, ++ (void *)page_to_pfn(pte->page), ++ (void *)page_to_phys(pte->page)); ++ ++ /* clean up */ ++ free_page(page_addr); ++ vfree(pte->sublevel); ++ memset(pte, 0, sizeof(struct gasket_page_table_entry)); ++ ++ return -ENOMEM; ++ } ++ ++ /* make the addresses available to the device */ ++ dma_addr = (pte->dma_addr + pte->offset) | GASKET_VALID_SLOT_FLAG; ++ writeq(dma_addr, slot); ++ ++ pte->flags = SET(FLAGS_STATUS, pte->flags, PTE_INUSE); ++ ++ return 0; ++} ++ ++/* ++ * Allocate slots in an extended page table. Check to see if a range of page ++ * table slots are available. If necessary, memory is allocated for second level ++ * page tables. ++ * ++ * Note that memory for second level page tables is allocated as needed, but ++ * that memory is only freed on the final close of the device file, when the ++ * page tables are repartitioned, or the the device is removed. If there is an ++ * error or if the full range of slots is not available, any memory ++ * allocated for second level page tables remains allocated until final close, ++ * repartition, or device removal. ++ * ++ * The page table mutex must be held by the caller. ++ */ ++static int gasket_alloc_extended_entries(struct gasket_page_table *pg_tbl, ++ u64 dev_addr, uint num_entries) ++{ ++ int ret = 0; ++ uint remain, subtable_slot_idx, len; ++ struct gasket_page_table_entry *pte; ++ u64 __iomem *slot; ++ ++ remain = num_entries; ++ subtable_slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr); ++ pte = pg_tbl->entries + pg_tbl->num_simple_entries + ++ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); ++ slot = pg_tbl->base_slot + pg_tbl->num_simple_entries + ++ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); ++ ++ while (remain > 0) { ++ len = min(remain, ++ GASKET_PAGES_PER_SUBTABLE - subtable_slot_idx); ++ ++ if (GET(FLAGS_STATUS, pte->flags) == PTE_FREE) { ++ ret = gasket_alloc_extended_subtable(pg_tbl, pte, slot); ++ if (ret) { ++ dev_err(pg_tbl->device, ++ "no memory for extended addr subtable\n"); ++ return ret; ++ } ++ } else { ++ if (!gasket_is_pte_range_free(pte->sublevel + ++ subtable_slot_idx, len)) ++ return -EBUSY; ++ } ++ ++ remain -= len; ++ subtable_slot_idx = 0; ++ pte++; ++ slot++; ++ } ++ ++ return 0; ++} ++ ++/* ++ * gasket_map_extended_pages - Get and map buffers to extended addresses. ++ * If there is an error, no pages are mapped. ++ */ ++static int gasket_map_extended_pages(struct gasket_page_table *pg_tbl, ++ struct sg_page_iter *sg_iter, ++ ulong host_addr, u64 dev_addr, ++ uint num_pages, u32 flags) ++{ ++ int ret; ++ u64 dev_addr_end; ++ uint slot_idx, remain, len; ++ struct gasket_page_table_entry *pte; ++ u64 __iomem *slot_base; ++ ++ ret = gasket_alloc_extended_entries(pg_tbl, dev_addr, num_pages); ++ if (ret) { ++ dev_addr_end = dev_addr + (num_pages / PAGE_SIZE) - 1; ++ dev_err(pg_tbl->device, ++ "page table slots (%lu,%lu) (@ 0x%llx) to (%lu,%lu) " ++ "are not available\n", ++ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr), ++ gasket_extended_lvl1_page_idx(pg_tbl, dev_addr), ++ (long long unsigned int)dev_addr, ++ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr_end), ++ gasket_extended_lvl1_page_idx(pg_tbl, dev_addr_end)); ++ return ret; ++ } ++ ++ remain = num_pages; ++ slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr); ++ pte = pg_tbl->entries + pg_tbl->num_simple_entries + ++ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); ++ ++ while (remain > 0) { ++ len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx); ++ ++ slot_base = ++ (u64 __iomem *)(page_address(pte->page) + pte->offset); ++ ret = gasket_perform_mapping(pg_tbl, pte->sublevel + slot_idx, ++ slot_base + slot_idx, sg_iter, ++ host_addr, len, flags, 0); ++ if (ret) { ++ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, ++ num_pages); ++ return ret; ++ } ++ ++ /* ++ * Extended page tables are in DRAM so they need to be synced ++ * each time they are updated. ++ */ ++ dma_sync_single_for_device(pg_tbl->device, ++ pte->dma_addr + slot_idx * sizeof(u64), ++ len * sizeof(u64), DMA_TO_DEVICE); ++ ++ remain -= len; ++ slot_idx = 0; ++ pte++; ++ if (host_addr) ++ host_addr += len * PAGE_SIZE; ++ } ++ ++ return 0; ++} ++ ++/* ++ * See gasket_page_table.h for general description. ++ * ++ * gasket_page_table_map calls either gasket_map_simple_pages() or ++ * gasket_map_extended_pages() to actually perform the mapping. ++ * ++ * The page table mutex is held for the entire operation. ++ */ ++int gasket_page_table_map(struct gasket_page_table *pg_tbl, ulong host_addr, ++ u64 dev_addr, uint num_pages, u32 flags) ++{ ++ int ret; ++ ++ if (!num_pages) ++ return 0; ++ ++ mutex_lock(&pg_tbl->mutex); ++ ++ if (gasket_addr_is_simple(pg_tbl, dev_addr)) { ++ ret = gasket_map_simple_pages(pg_tbl, NULL, host_addr, dev_addr, ++ num_pages, flags); ++ } else { ++ ret = gasket_map_extended_pages(pg_tbl, NULL, host_addr, dev_addr, ++ num_pages, flags); ++ } ++ ++ mutex_unlock(&pg_tbl->mutex); ++ ++ dev_dbg(pg_tbl->device, ++ "%s done: ha %llx daddr %llx num %d, flags %x ret %d\n", ++ __func__, (unsigned long long)host_addr, ++ (unsigned long long)dev_addr, num_pages, flags, ret); ++ return ret; ++} ++EXPORT_SYMBOL(gasket_page_table_map); ++ ++/* ++ * See gasket_page_table.h for general description. ++ * ++ * gasket_page_table_unmap takes the page table lock and calls either ++ * gasket_unmap_simple_pages() or gasket_unmap_extended_pages() to ++ * actually unmap the pages from device space. ++ * ++ * The page table mutex is held for the entire operation. ++ */ ++void gasket_page_table_unmap(struct gasket_page_table *pg_tbl, u64 dev_addr, ++ uint num_pages) ++{ ++ if (!num_pages) ++ return; ++ ++ mutex_lock(&pg_tbl->mutex); ++ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages); ++ mutex_unlock(&pg_tbl->mutex); ++} ++EXPORT_SYMBOL(gasket_page_table_unmap); ++ ++int gasket_page_table_map_dmabuf(struct gasket_page_table *pg_tbl, int fd, ++ u64 dev_addr, uint num_pages, u32 flags) ++{ ++ int ret, locked = 0; ++ struct dma_buf *dmabuf = NULL; ++ struct dma_buf_attachment *attachment = NULL; ++ struct sg_table *sgt = NULL; ++ struct sg_page_iter sg_iter; ++ struct gasket_dmabuf_mapping *mapping = NULL; ++ enum dma_data_direction direction = GET(FLAGS_DMA_DIRECTION, flags); ++ ++ if (direction == DMA_NONE) { ++ dev_err(pg_tbl->device, ++ "invalid DMA direction flags=0x%x\n", flags); ++ return -EINVAL; ++ } ++ ++ if (!num_pages) ++ return 0; ++ ++ dmabuf = dma_buf_get(fd); ++ if (IS_ERR(dmabuf)) ++ return PTR_ERR(dmabuf); ++ ++ if (PAGE_ALIGN(dmabuf->size) / PAGE_SIZE < num_pages) ++ return -EINVAL; ++ ++ mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); ++ if (!mapping) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ attachment = dma_buf_attach(dmabuf, pg_tbl->device); ++ if (IS_ERR(attachment)) { ++ ret = PTR_ERR(attachment); ++ goto out; ++ } ++ ++ sgt = dma_buf_map_attachment(attachment, direction); ++ if (IS_ERR(sgt)) { ++ ret = PTR_ERR(sgt); ++ goto out; ++ } ++ ++ mutex_lock(&pg_tbl->mutex); ++ locked = 1; ++ ++ __sg_page_iter_start(&sg_iter, sgt->sgl, sgt->nents, 0); ++ if (gasket_addr_is_simple(pg_tbl, dev_addr)) { ++ ret = gasket_map_simple_pages(pg_tbl, &sg_iter, 0, dev_addr, ++ num_pages, flags); ++ } else { ++ ret = gasket_map_extended_pages(pg_tbl, &sg_iter, 0, dev_addr, ++ num_pages, flags); ++ } ++ ++ if (!ret) { ++ INIT_LIST_HEAD(&mapping->list); ++ get_dma_buf(dmabuf); ++ mapping->dmabuf = dmabuf; ++ mapping->attachment = attachment; ++ mapping->sgt = sgt; ++ mapping->direction = direction; ++ list_add(&mapping->list, &pg_tbl->dmabufs); ++ sgt = NULL; ++ attachment = NULL; ++ mapping = NULL; ++ } ++ ++out: ++ if (locked) ++ mutex_unlock(&pg_tbl->mutex); ++ ++ if (!IS_ERR_OR_NULL(sgt)) ++ dma_buf_unmap_attachment(attachment, sgt, direction); ++ ++ if (!IS_ERR_OR_NULL(attachment)) ++ dma_buf_detach(dmabuf, attachment); ++ ++ kfree(mapping); ++ dma_buf_put(dmabuf); ++ ++ return ret; ++} ++EXPORT_SYMBOL(gasket_page_table_map_dmabuf); ++ ++/* Detach dmabuf from our device if attached, NULL to detach all. */ ++static void gasket_page_table_detach_dmabuf_nolock(struct gasket_page_table *pg_tbl, ++ struct dma_buf *dmabuf) ++{ ++ struct gasket_dmabuf_mapping *mapping, *tmp; ++ ++ list_for_each_entry_safe(mapping, tmp, &pg_tbl->dmabufs, list) { ++ if (!dmabuf || mapping->dmabuf == dmabuf) { ++ dma_buf_unmap_attachment(mapping->attachment, ++ mapping->sgt, ++ mapping->direction); ++ dma_buf_detach(mapping->dmabuf, mapping->attachment); ++ dma_buf_put(mapping->dmabuf); ++ list_del(&mapping->list); ++ kfree(mapping); ++ } ++ } ++} ++ ++int gasket_page_table_unmap_dmabuf(struct gasket_page_table *pg_tbl, int fd, ++ u64 dev_addr, uint num_pages) ++{ ++ struct dma_buf *dmabuf; ++ ++ dmabuf = dma_buf_get(fd); ++ if (IS_ERR(dmabuf)) ++ return PTR_ERR(dmabuf); ++ ++ if (PAGE_ALIGN(dmabuf->size) / PAGE_SIZE < num_pages) { ++ dma_buf_put(dmabuf); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&pg_tbl->mutex); ++ ++ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages); ++ gasket_page_table_detach_dmabuf_nolock(pg_tbl, dmabuf); ++ ++ mutex_unlock(&pg_tbl->mutex); ++ ++ dma_buf_put(dmabuf); ++ ++ return 0; ++} ++EXPORT_SYMBOL(gasket_page_table_unmap_dmabuf); ++ ++static void gasket_page_table_unmap_all_nolock(struct gasket_page_table *pg_tbl) ++{ ++ gasket_page_table_detach_dmabuf_nolock(pg_tbl, NULL); ++ ++ gasket_unmap_simple_pages(pg_tbl, ++ gasket_components_to_dev_address(pg_tbl, 1, 0, ++ 0), ++ pg_tbl->num_simple_entries); ++ gasket_unmap_extended_pages(pg_tbl, ++ gasket_components_to_dev_address(pg_tbl, 0, ++ 0, 0), ++ pg_tbl->num_extended_entries * ++ GASKET_PAGES_PER_SUBTABLE); ++} ++ ++/* See gasket_page_table.h for description. */ ++void gasket_page_table_unmap_all(struct gasket_page_table *pg_tbl) ++{ ++ mutex_lock(&pg_tbl->mutex); ++ gasket_page_table_unmap_all_nolock(pg_tbl); ++ mutex_unlock(&pg_tbl->mutex); ++} ++EXPORT_SYMBOL(gasket_page_table_unmap_all); ++ ++/* See gasket_page_table.h for description. */ ++void gasket_page_table_reset(struct gasket_page_table *pg_tbl) ++{ ++ mutex_lock(&pg_tbl->mutex); ++ gasket_page_table_unmap_all_nolock(pg_tbl); ++ writeq(pg_tbl->config.total_entries, pg_tbl->extended_offset_reg); ++ mutex_unlock(&pg_tbl->mutex); ++} ++ ++/* See gasket_page_table.h for description. */ ++int gasket_page_table_lookup_page( ++ struct gasket_page_table *pg_tbl, u64 dev_addr, struct page **ppage, ++ ulong *poffset) ++{ ++ uint page_num; ++ struct gasket_page_table_entry *pte; ++ ++ mutex_lock(&pg_tbl->mutex); ++ if (gasket_addr_is_simple(pg_tbl, dev_addr)) { ++ page_num = gasket_simple_page_idx(pg_tbl, dev_addr); ++ if (page_num >= pg_tbl->num_simple_entries) ++ goto fail; ++ ++ pte = pg_tbl->entries + page_num; ++ if (GET(FLAGS_STATUS, pte->flags) != PTE_INUSE) ++ goto fail; ++ } else { ++ /* Find the level 0 entry, */ ++ page_num = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); ++ if (page_num >= pg_tbl->num_extended_entries) ++ goto fail; ++ ++ pte = pg_tbl->entries + pg_tbl->num_simple_entries + page_num; ++ if (GET(FLAGS_STATUS, pte->flags) != PTE_INUSE) ++ goto fail; ++ ++ /* and its contained level 1 entry. */ ++ page_num = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr); ++ pte = pte->sublevel + page_num; ++ if (GET(FLAGS_STATUS, pte->flags) != PTE_INUSE) ++ goto fail; ++ } ++ ++ *ppage = pte->page; ++ *poffset = pte->offset; ++ mutex_unlock(&pg_tbl->mutex); ++ return 0; ++ ++fail: ++ *ppage = NULL; ++ *poffset = 0; ++ mutex_unlock(&pg_tbl->mutex); ++ return -EINVAL; ++} ++ ++/* See gasket_page_table.h for description. */ ++bool gasket_page_table_are_addrs_bad( ++ struct gasket_page_table *pg_tbl, ulong host_addr, u64 dev_addr, ++ ulong bytes) ++{ ++ if (host_addr & (PAGE_SIZE - 1)) { ++ dev_err(pg_tbl->device, ++ "host mapping address 0x%lx must be page aligned\n", ++ host_addr); ++ return true; ++ } ++ ++ return gasket_page_table_is_dev_addr_bad(pg_tbl, dev_addr, bytes); ++} ++EXPORT_SYMBOL(gasket_page_table_are_addrs_bad); ++ ++/* See gasket_page_table.h for description. */ ++bool gasket_page_table_is_dev_addr_bad( ++ struct gasket_page_table *pg_tbl, u64 dev_addr, ulong bytes) ++{ ++ uint num_pages = bytes / PAGE_SIZE; ++ ++ if (bytes & (PAGE_SIZE - 1)) { ++ dev_err(pg_tbl->device, ++ "mapping size 0x%lX must be page aligned\n", bytes); ++ return true; ++ } ++ ++ if (num_pages == 0) { ++ dev_err(pg_tbl->device, ++ "requested mapping is less than one page: %lu / %lu\n", ++ bytes, PAGE_SIZE); ++ return true; ++ } ++ ++ if (gasket_addr_is_simple(pg_tbl, dev_addr)) ++ return gasket_is_simple_dev_addr_bad(pg_tbl, dev_addr, ++ num_pages); ++ return gasket_is_extended_dev_addr_bad(pg_tbl, dev_addr, num_pages); ++} ++EXPORT_SYMBOL(gasket_page_table_is_dev_addr_bad); ++ ++/* See gasket_page_table.h for description. */ ++uint gasket_page_table_max_size(struct gasket_page_table *page_table) ++{ ++ if (!page_table) ++ return 0; ++ return page_table->config.total_entries; ++} ++EXPORT_SYMBOL(gasket_page_table_max_size); ++ ++/* See gasket_page_table.h for description. */ ++uint gasket_page_table_num_entries(struct gasket_page_table *pg_tbl) ++{ ++ if (!pg_tbl) ++ return 0; ++ return pg_tbl->num_simple_entries + pg_tbl->num_extended_entries; ++} ++EXPORT_SYMBOL(gasket_page_table_num_entries); ++ ++/* See gasket_page_table.h for description. */ ++uint gasket_page_table_num_simple_entries(struct gasket_page_table *pg_tbl) ++{ ++ if (!pg_tbl) ++ return 0; ++ return pg_tbl->num_simple_entries; ++} ++EXPORT_SYMBOL(gasket_page_table_num_simple_entries); ++ ++/* See gasket_page_table.h for description. */ ++uint gasket_page_table_num_active_pages(struct gasket_page_table *pg_tbl) ++{ ++ if (!pg_tbl) ++ return 0; ++ return pg_tbl->num_active_pages; ++} ++EXPORT_SYMBOL(gasket_page_table_num_active_pages); ++ ++/* See gasket_page_table.h */ ++int gasket_page_table_system_status(struct gasket_page_table *page_table) ++{ ++ if (!page_table) ++ return GASKET_STATUS_LAMED; ++ ++ if (gasket_page_table_num_entries(page_table) == 0) { ++ dev_dbg(page_table->device, "Page table size is 0\n"); ++ return GASKET_STATUS_LAMED; ++ } ++ ++ return GASKET_STATUS_ALIVE; ++} ++ ++/* Record the host_addr to coherent dma memory mapping. */ ++int gasket_set_user_virt( ++ struct gasket_dev *gasket_dev, u64 size, dma_addr_t dma_address, ++ ulong vma) ++{ ++ int j; ++ struct gasket_page_table *pg_tbl; ++ ++ unsigned int num_pages = size / PAGE_SIZE; ++ ++ /* ++ * TODO: for future chipset, better handling of the case where multiple ++ * page tables are supported on a given device ++ */ ++ pg_tbl = gasket_dev->page_table[0]; ++ if (!pg_tbl) { ++ dev_dbg(gasket_dev->dev, "%s: invalid page table index\n", ++ __func__); ++ return 0; ++ } ++ for (j = 0; j < num_pages; j++) { ++ pg_tbl->coherent_pages[j].user_virt = ++ (u64)vma + j * PAGE_SIZE; ++ } ++ return 0; ++} ++ ++/* Allocate a block of coherent memory. */ ++int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, u64 size, ++ dma_addr_t *dma_address, u64 index) ++{ ++ dma_addr_t handle; ++ void *mem; ++ int j; ++ unsigned int num_pages = (size + PAGE_SIZE - 1) / PAGE_SIZE; ++ const struct gasket_driver_desc *driver_desc = ++ gasket_get_driver_desc(gasket_dev); ++ ++ if (!gasket_dev->page_table[index]) ++ return -EFAULT; ++ ++ if (num_pages == 0) ++ return -EINVAL; ++ ++ mem = dma_alloc_coherent(gasket_get_device(gasket_dev), ++ num_pages * PAGE_SIZE, &handle, GFP_KERNEL); ++ if (!mem) ++ goto nomem; ++ ++ gasket_dev->page_table[index]->num_coherent_pages = num_pages; ++ ++ /* allocate the physical memory block */ ++ gasket_dev->page_table[index]->coherent_pages = ++ kcalloc(num_pages, sizeof(struct gasket_coherent_page_entry), ++ GFP_KERNEL); ++ if (!gasket_dev->page_table[index]->coherent_pages) ++ goto nomem; ++ ++ gasket_dev->coherent_buffer.length_bytes = ++ PAGE_SIZE * (num_pages); ++ gasket_dev->coherent_buffer.phys_base = handle; ++ gasket_dev->coherent_buffer.virt_base = mem; ++ ++ *dma_address = driver_desc->coherent_buffer_description.base; ++ for (j = 0; j < num_pages; j++) { ++ gasket_dev->page_table[index]->coherent_pages[j].paddr = ++ handle + j * PAGE_SIZE; ++ gasket_dev->page_table[index]->coherent_pages[j].kernel_virt = ++ (ulong)mem + j * PAGE_SIZE; ++ } ++ ++ return 0; ++ ++nomem: ++ if (mem) { ++ dma_free_coherent(gasket_get_device(gasket_dev), ++ num_pages * PAGE_SIZE, mem, handle); ++ gasket_dev->coherent_buffer.length_bytes = 0; ++ gasket_dev->coherent_buffer.virt_base = NULL; ++ gasket_dev->coherent_buffer.phys_base = 0; ++ } ++ ++ kfree(gasket_dev->page_table[index]->coherent_pages); ++ gasket_dev->page_table[index]->coherent_pages = NULL; ++ gasket_dev->page_table[index]->num_coherent_pages = 0; ++ return -ENOMEM; ++} ++ ++/* Free a block of coherent memory. */ ++int gasket_free_coherent_memory(struct gasket_dev *gasket_dev, u64 size, ++ dma_addr_t dma_address, u64 index) ++{ ++ const struct gasket_driver_desc *driver_desc; ++ ++ if (!gasket_dev->page_table[index]) ++ return -EFAULT; ++ ++ driver_desc = gasket_get_driver_desc(gasket_dev); ++ ++ if (driver_desc->coherent_buffer_description.base != dma_address) ++ return -EADDRNOTAVAIL; ++ ++ gasket_free_coherent_memory_all(gasket_dev, index); ++ ++ return 0; ++} ++ ++/* Release all coherent memory. */ ++void gasket_free_coherent_memory_all( ++ struct gasket_dev *gasket_dev, u64 index) ++{ ++ if (!gasket_dev->page_table[index]) ++ return; ++ ++ if (gasket_dev->coherent_buffer.length_bytes) { ++ dma_free_coherent(gasket_get_device(gasket_dev), ++ gasket_dev->coherent_buffer.length_bytes, ++ gasket_dev->coherent_buffer.virt_base, ++ gasket_dev->coherent_buffer.phys_base); ++ gasket_dev->coherent_buffer.length_bytes = 0; ++ gasket_dev->coherent_buffer.virt_base = NULL; ++ gasket_dev->coherent_buffer.phys_base = 0; ++ } ++ ++ kfree(gasket_dev->page_table[index]->coherent_pages); ++ gasket_dev->page_table[index]->coherent_pages = NULL; ++ gasket_dev->page_table[index]->num_coherent_pages = 0; ++} +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_page_table.h b/drivers/staging/gasket-driver/gasket_page_table.h +--- a/drivers/staging/gasket-driver/gasket_page_table.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_page_table.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,283 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Gasket Page Table functionality. This file describes the address ++ * translation/paging functionality supported by the Gasket driver framework. ++ * As much as possible, internal details are hidden to simplify use - ++ * all calls are thread-safe (protected by an internal mutex) except where ++ * indicated otherwise. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++ ++#ifndef __GASKET_PAGE_TABLE_H__ ++#define __GASKET_PAGE_TABLE_H__ ++ ++#include ++#include ++ ++#include "gasket_constants.h" ++#include "gasket_core.h" ++ ++/* ++ * Structure used for managing address translation on a device. All details are ++ * internal to the implementation. ++ */ ++struct gasket_page_table; ++ ++/* ++ * Allocate and init address translation data. ++ * @ppage_table: Pointer to Gasket page table pointer. Set by this call. ++ * @att_base_reg: [Mapped] pointer to the first entry in the device's address ++ * translation table. ++ * @extended_offset_reg: [Mapped] pointer to the device's register containing ++ * the starting index of the extended translation table. ++ * @extended_bit_location: The index of the bit indicating whether an address ++ * is extended. ++ * @total_entries: The total number of entries in the device's address ++ * translation table. ++ * @device: Device structure for the underlying device. Only used for logging. ++ * @pci_dev: PCI system descriptor for the underlying device. ++ * whether the driver will supply its own. ++ * ++ * Description: Allocates and initializes data to track address translation - ++ * simple and extended page table metadata. Initially, the page table is ++ * partitioned such that all addresses are "simple" (single-level lookup). ++ * gasket_partition_page_table can be called to change this paritioning. ++ * ++ * Returns 0 on success, a negative error code otherwise. ++ */ ++int gasket_page_table_init(struct gasket_page_table **ppg_tbl, ++ const struct gasket_bar_data *bar_data, ++ const struct gasket_page_table_config *page_table_config, ++ struct device *device, struct pci_dev *pci_dev); ++ ++/* ++ * Deallocate and cleanup page table data. ++ * @page_table: Gasket page table pointer. ++ * ++ * Description: The inverse of gasket_init; frees page_table and its contained ++ * elements. ++ * ++ * Because this call destroys the page table, it cannot be ++ * thread-safe (mutex-protected)! ++ */ ++void gasket_page_table_cleanup(struct gasket_page_table *page_table); ++ ++/* ++ * Sets the size of the simple page table. ++ * @page_table: Gasket page table pointer. ++ * @num_simple_entries: Desired size of the simple page table (in entries). ++ * ++ * Description: gasket_partition_page_table checks to see if the simple page ++ * size can be changed (i.e., if there are no active extended ++ * mappings in the new simple size range), and, if so, ++ * sets the new simple and extended page table sizes. ++ * ++ * Returns 0 if successful, or non-zero if the page table entries ++ * are not free. ++ */ ++int gasket_page_table_partition(struct gasket_page_table *page_table, ++ uint num_simple_entries); ++ ++/* ++ * Get and map [host] user space pages into device memory. ++ * @page_table: Gasket page table pointer. ++ * @host_addr: Starting host virtual memory address of the pages. ++ * @dev_addr: Starting device address of the pages. ++ * @num_pages: Number of [4kB] pages to map. ++ * @flags: Specifies attributes to apply to the pages. ++ * Internal structure matches gasket_page_table_ioctl_flags.flags. ++ * ++ * Description: Maps the "num_pages" pages of host memory pointed to by ++ * host_addr to the address "dev_addr" in device memory. ++ * ++ * The caller is responsible for checking the addresses ranges. ++ * ++ * Returns 0 if successful or a non-zero error number otherwise. ++ * If there is an error, no pages are mapped. ++ */ ++int gasket_page_table_map(struct gasket_page_table *page_table, ulong host_addr, ++ u64 dev_addr, uint num_pages, u32 flags); ++ ++/* ++ * Map dma-buf pages into device memory. ++ * @page_table: Gasket page table pointer. ++ * @fd: Dma-buf file descriptor. ++ * @dev_addr: Starting device address of the pages. ++ * @num_pages: Number of [4kB] pages to map. ++ * @flags: Specifies attributes to apply to the pages. ++ * Internal structure matches gasket_page_table_ioctl_flags.flags. ++ * ++ * Description: Maps "num_pages" pages of dma-buf pointed to by ++ * fd to the address "dev_addr" in device memory. ++ * ++ * The caller is responsible for checking the dev_addr range. ++ * ++ * Returns 0 if successful or a non-zero error number otherwise. ++ * If there is an error, no pages are mapped. ++ */ ++int gasket_page_table_map_dmabuf(struct gasket_page_table *page_table, int fd, ++ u64 dev_addr, uint num_pages, u32 flags); ++ ++/* ++ * Unmap dma-buf pages from device memory. ++ * @page_table: Gasket page table pointer. ++ * @fd: Dma-buf file descriptor. ++ * @dev_addr: Starting device address of the pages. ++ * @num_pages: Number of [4kB] pages to map. ++ * ++ * Description: The inverse of gasket_page_table_map_dmabuf. ++ */ ++int gasket_page_table_unmap_dmabuf(struct gasket_page_table *page_table, int fd, ++ u64 dev_addr, uint num_pages); ++ ++/* ++ * Un-map host pages from device memory. ++ * @page_table: Gasket page table pointer. ++ * @dev_addr: Starting device address of the pages to unmap. ++ * @num_pages: The number of [4kB] pages to unmap. ++ * ++ * Description: The inverse of gasket_map_pages. Unmaps pages from the device. ++ */ ++void gasket_page_table_unmap(struct gasket_page_table *page_table, ++ u64 dev_addr, uint num_pages); ++ ++/* ++ * Unmap ALL host pages from device memory. ++ * @page_table: Gasket page table pointer. ++ */ ++void gasket_page_table_unmap_all(struct gasket_page_table *page_table); ++ ++/* ++ * Unmap all host pages from device memory and reset the table to fully simple ++ * addressing. ++ * @page_table: Gasket page table pointer. ++ */ ++void gasket_page_table_reset(struct gasket_page_table *page_table); ++ ++/* ++ * Reclaims unused page table memory. ++ * @page_table: Gasket page table pointer. ++ * ++ * Description: Examines the page table and frees any currently-unused ++ * allocations. Called internally on gasket_cleanup(). ++ */ ++void gasket_page_table_garbage_collect(struct gasket_page_table *page_table); ++ ++/* ++ * Retrieve the backing page for a device address. ++ * @page_table: Gasket page table pointer. ++ * @dev_addr: Gasket device address. ++ * @ppage: Pointer to a page pointer for the returned page. ++ * @poffset: Pointer to an unsigned long for the returned offset. ++ * ++ * Description: Interprets the address and looks up the corresponding page ++ * in the page table and the offset in that page. (We need an ++ * offset because the host page may be larger than the Gasket chip ++ * page it contains.) ++ * ++ * Returns 0 if successful, -1 for an error. The page pointer ++ * and offset are returned through the pointers, if successful. ++ */ ++int gasket_page_table_lookup_page(struct gasket_page_table *page_table, ++ u64 dev_addr, struct page **page, ++ ulong *poffset); ++ ++/* ++ * Checks validity for input addrs and size. ++ * @page_table: Gasket page table pointer. ++ * @host_addr: Host address to check. ++ * @dev_addr: Gasket device address. ++ * @bytes: Size of the range to check (in bytes). ++ * ++ * Description: This call performs a number of checks to verify that the ranges ++ * specified by both addresses and the size are valid for mapping pages into ++ * device memory. ++ * ++ * Returns true if the mapping is bad, false otherwise. ++ */ ++bool gasket_page_table_are_addrs_bad(struct gasket_page_table *page_table, ++ ulong host_addr, u64 dev_addr, ++ ulong bytes); ++ ++/* ++ * Checks validity for input dev addr and size. ++ * @page_table: Gasket page table pointer. ++ * @dev_addr: Gasket device address. ++ * @bytes: Size of the range to check (in bytes). ++ * ++ * Description: This call performs a number of checks to verify that the range ++ * specified by the device address and the size is valid for mapping pages into ++ * device memory. ++ * ++ * Returns true if the address is bad, false otherwise. ++ */ ++bool gasket_page_table_is_dev_addr_bad(struct gasket_page_table *page_table, ++ u64 dev_addr, ulong bytes); ++ ++/* ++ * Gets maximum size for the given page table. ++ * @page_table: Gasket page table pointer. ++ */ ++uint gasket_page_table_max_size(struct gasket_page_table *page_table); ++ ++/* ++ * Gets the total number of entries in the arg. ++ * @page_table: Gasket page table pointer. ++ */ ++uint gasket_page_table_num_entries(struct gasket_page_table *page_table); ++ ++/* ++ * Gets the number of simple entries. ++ * @page_table: Gasket page table pointer. ++ */ ++uint gasket_page_table_num_simple_entries(struct gasket_page_table *page_table); ++ ++/* ++ * Gets the number of actively pinned pages. ++ * @page_table: Gasket page table pointer. ++ */ ++uint gasket_page_table_num_active_pages(struct gasket_page_table *page_table); ++ ++/* ++ * Get status of page table managed by @page_table. ++ * @page_table: Gasket page table pointer. ++ */ ++int gasket_page_table_system_status(struct gasket_page_table *page_table); ++ ++/* ++ * Allocate a block of coherent memory. ++ * @gasket_dev: Gasket Device. ++ * @size: Size of the memory block. ++ * @dma_address: Dma address allocated by the kernel. ++ * @index: Index of the gasket_page_table within this Gasket device ++ * ++ * Description: Allocate a contiguous coherent memory block, DMA'ble ++ * by this device. ++ */ ++int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, uint64_t size, ++ dma_addr_t *dma_address, uint64_t index); ++/* Release a block of contiguous coherent memory, in use by a device. */ ++int gasket_free_coherent_memory(struct gasket_dev *gasket_dev, uint64_t size, ++ dma_addr_t dma_address, uint64_t index); ++ ++/* Release all coherent memory. */ ++void gasket_free_coherent_memory_all(struct gasket_dev *gasket_dev, ++ uint64_t index); ++ ++/* ++ * Records the host_addr to coherent dma memory mapping. ++ * @gasket_dev: Gasket Device. ++ * @size: Size of the virtual address range to map. ++ * @dma_address: Dma address within the coherent memory range. ++ * @vma: Virtual address we wish to map to coherent memory. ++ * ++ * Description: For each page in the virtual address range, record the ++ * coherent page mapping. ++ * ++ * Does not perform validity checking. ++ */ ++int gasket_set_user_virt(struct gasket_dev *gasket_dev, uint64_t size, ++ dma_addr_t dma_address, ulong vma); ++ ++#endif /* __GASKET_PAGE_TABLE_H__ */ +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_sysfs.c b/drivers/staging/gasket-driver/gasket_sysfs.c +--- a/drivers/staging/gasket-driver/gasket_sysfs.c 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_sysfs.c 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,400 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright (C) 2018 Google, Inc. */ ++#include "gasket_sysfs.h" ++ ++#include "gasket_core.h" ++ ++#include ++#include ++ ++/* ++ * Pair of kernel device and user-specified pointer. Used in lookups in sysfs ++ * "show" functions to return user data. ++ */ ++ ++struct gasket_sysfs_mapping { ++ /* ++ * The device bound to this mapping. If this is NULL, then this mapping ++ * is free. ++ */ ++ struct device *device; ++ ++ /* The Gasket descriptor for this device. */ ++ struct gasket_dev *gasket_dev; ++ ++ /* This device's set of sysfs attributes/nodes. */ ++ struct gasket_sysfs_attribute *attributes; ++ ++ /* The number of live elements in "attributes". */ ++ int attribute_count; ++ ++ /* Protects structure from simultaneous access. */ ++ struct mutex mutex; ++ ++ /* Tracks active users of this mapping. */ ++ struct kref refcount; ++}; ++ ++/* ++ * Data needed to manage users of this sysfs utility. ++ * Currently has a fixed size; if space is a concern, this can be dynamically ++ * allocated. ++ */ ++/* ++ * 'Global' (file-scoped) list of mappings between devices and gasket_data ++ * pointers. This removes the requirement to have a gasket_sysfs_data ++ * handle in all files. ++ */ ++static struct gasket_sysfs_mapping dev_mappings[GASKET_SYSFS_NUM_MAPPINGS]; ++ ++/* Callback when a mapping's refcount goes to zero. */ ++static void release_entry(struct kref *ref) ++{ ++ /* All work is done after the return from kref_put. */ ++} ++ ++/* Look up mapping information for the given device. */ ++static struct gasket_sysfs_mapping *get_mapping(struct device *device) ++{ ++ int i; ++ ++ for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) { ++ mutex_lock(&dev_mappings[i].mutex); ++ if (dev_mappings[i].device == device) { ++ kref_get(&dev_mappings[i].refcount); ++ mutex_unlock(&dev_mappings[i].mutex); ++ return &dev_mappings[i]; ++ } ++ mutex_unlock(&dev_mappings[i].mutex); ++ } ++ ++ dev_dbg(device, "%s: Mapping to device %s not found\n", ++ __func__, device->kobj.name); ++ return NULL; ++} ++ ++/* Put a reference to a mapping. */ ++static void put_mapping(struct gasket_sysfs_mapping *mapping) ++{ ++ int i; ++ int num_files_to_remove = 0; ++ struct device_attribute *files_to_remove; ++ struct device *device; ++ ++ if (!mapping) { ++ pr_debug("%s: Mapping should not be NULL\n", __func__); ++ return; ++ } ++ ++ mutex_lock(&mapping->mutex); ++ if (kref_put(&mapping->refcount, release_entry)) { ++ dev_dbg(mapping->device, "Removing Gasket sysfs mapping\n"); ++ /* ++ * We can't remove the sysfs nodes in the kref callback, since ++ * device_remove_file() blocks until the node is free. ++ * Readers/writers of sysfs nodes, though, will be blocked on ++ * the mapping mutex, resulting in deadlock. To fix this, the ++ * sysfs nodes are removed outside the lock. ++ */ ++ device = mapping->device; ++ num_files_to_remove = mapping->attribute_count; ++ files_to_remove = kcalloc(num_files_to_remove, ++ sizeof(*files_to_remove), ++ GFP_KERNEL); ++ if (files_to_remove) ++ for (i = 0; i < num_files_to_remove; i++) ++ files_to_remove[i] = ++ mapping->attributes[i].attr; ++ else ++ num_files_to_remove = 0; ++ ++ kfree(mapping->attributes); ++ mapping->attributes = NULL; ++ mapping->attribute_count = 0; ++ put_device(mapping->device); ++ mapping->device = NULL; ++ mapping->gasket_dev = NULL; ++ } ++ mutex_unlock(&mapping->mutex); ++ ++ if (num_files_to_remove != 0) { ++ for (i = 0; i < num_files_to_remove; ++i) ++ device_remove_file(device, &files_to_remove[i]); ++ kfree(files_to_remove); ++ } ++} ++ ++/* ++ * Put a reference to a mapping N times. ++ * ++ * In higher-level resource acquire/release function pairs, the release function ++ * will need to release a mapping 2x - once for the refcount taken in the ++ * release function itself, and once for the count taken in the acquire call. ++ */ ++static void put_mapping_n(struct gasket_sysfs_mapping *mapping, int times) ++{ ++ int i; ++ ++ for (i = 0; i < times; i++) ++ put_mapping(mapping); ++} ++ ++void gasket_sysfs_init(void) ++{ ++ int i; ++ ++ for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) { ++ dev_mappings[i].device = NULL; ++ mutex_init(&dev_mappings[i].mutex); ++ } ++} ++ ++int gasket_sysfs_create_mapping(struct device *device, ++ struct gasket_dev *gasket_dev) ++{ ++ struct gasket_sysfs_mapping *mapping; ++ int map_idx = -1; ++ ++ /* ++ * We need a function-level mutex to protect against the same device ++ * being added [multiple times] simultaneously. ++ */ ++ static DEFINE_MUTEX(function_mutex); ++ ++ mutex_lock(&function_mutex); ++ dev_dbg(device, "Creating sysfs entries for device\n"); ++ ++ /* Check that the device we're adding hasn't already been added. */ ++ mapping = get_mapping(device); ++ if (mapping) { ++ dev_err(device, ++ "Attempting to re-initialize sysfs mapping for device\n"); ++ put_mapping(mapping); ++ mutex_unlock(&function_mutex); ++ return -EBUSY; ++ } ++ ++ /* Find the first empty entry in the array. */ ++ for (map_idx = 0; map_idx < GASKET_SYSFS_NUM_MAPPINGS; ++map_idx) { ++ mutex_lock(&dev_mappings[map_idx].mutex); ++ if (!dev_mappings[map_idx].device) ++ /* Break with the mutex held! */ ++ break; ++ mutex_unlock(&dev_mappings[map_idx].mutex); ++ } ++ ++ if (map_idx == GASKET_SYSFS_NUM_MAPPINGS) { ++ dev_err(device, "All mappings have been exhausted\n"); ++ mutex_unlock(&function_mutex); ++ return -ENOMEM; ++ } ++ ++ dev_dbg(device, "Creating sysfs mapping for device %s\n", ++ device->kobj.name); ++ ++ mapping = &dev_mappings[map_idx]; ++ mapping->attributes = kcalloc(GASKET_SYSFS_MAX_NODES, ++ sizeof(*mapping->attributes), ++ GFP_KERNEL); ++ if (!mapping->attributes) { ++ dev_dbg(device, "Unable to allocate sysfs attribute array\n"); ++ mutex_unlock(&mapping->mutex); ++ mutex_unlock(&function_mutex); ++ return -ENOMEM; ++ } ++ ++ kref_init(&mapping->refcount); ++ mapping->device = get_device(device); ++ mapping->gasket_dev = gasket_dev; ++ mapping->attribute_count = 0; ++ mutex_unlock(&mapping->mutex); ++ mutex_unlock(&function_mutex); ++ ++ /* Don't decrement the refcount here! One open count keeps it alive! */ ++ return 0; ++} ++ ++int gasket_sysfs_create_entries(struct device *device, ++ const struct gasket_sysfs_attribute *attrs) ++{ ++ int i; ++ int ret; ++ struct gasket_sysfs_mapping *mapping = get_mapping(device); ++ ++ if (!mapping) { ++ dev_dbg(device, ++ "Creating entries for device without first " ++ "initializing mapping\n"); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&mapping->mutex); ++ for (i = 0; strcmp(attrs[i].attr.attr.name, GASKET_ARRAY_END_MARKER); ++ i++) { ++ if (mapping->attribute_count == GASKET_SYSFS_MAX_NODES) { ++ dev_err(device, ++ "Maximum number of sysfs nodes reached for " ++ "device\n"); ++ mutex_unlock(&mapping->mutex); ++ put_mapping(mapping); ++ return -ENOMEM; ++ } ++ ++ ret = device_create_file(device, &attrs[i].attr); ++ if (ret) { ++ dev_dbg(device, "Unable to create device entries\n"); ++ mutex_unlock(&mapping->mutex); ++ put_mapping(mapping); ++ return ret; ++ } ++ ++ mapping->attributes[mapping->attribute_count] = attrs[i]; ++ ++mapping->attribute_count; ++ } ++ ++ mutex_unlock(&mapping->mutex); ++ put_mapping(mapping); ++ return 0; ++} ++EXPORT_SYMBOL(gasket_sysfs_create_entries); ++ ++void gasket_sysfs_remove_mapping(struct device *device) ++{ ++ struct gasket_sysfs_mapping *mapping = get_mapping(device); ++ ++ if (!mapping) { ++ dev_err(device, ++ "Attempted to remove non-existent sysfs mapping to " ++ "device\n"); ++ return; ++ } ++ ++ put_mapping_n(mapping, 2); ++} ++ ++struct gasket_dev *gasket_sysfs_get_device_data(struct device *device) ++{ ++ struct gasket_sysfs_mapping *mapping = get_mapping(device); ++ ++ if (!mapping) { ++ dev_err(device, "device not registered\n"); ++ return NULL; ++ } ++ ++ return mapping->gasket_dev; ++} ++EXPORT_SYMBOL(gasket_sysfs_get_device_data); ++ ++void gasket_sysfs_put_device_data(struct device *device, struct gasket_dev *dev) ++{ ++ struct gasket_sysfs_mapping *mapping = get_mapping(device); ++ ++ if (!mapping) ++ return; ++ ++ /* See comment of put_mapping_n() for why the '2' is necessary. */ ++ put_mapping_n(mapping, 2); ++} ++EXPORT_SYMBOL(gasket_sysfs_put_device_data); ++ ++struct gasket_sysfs_attribute * ++gasket_sysfs_get_attr(struct device *device, struct device_attribute *attr) ++{ ++ int i; ++ int num_attrs; ++ struct gasket_sysfs_mapping *mapping = get_mapping(device); ++ struct gasket_sysfs_attribute *attrs = NULL; ++ ++ if (!mapping) ++ return NULL; ++ ++ attrs = mapping->attributes; ++ num_attrs = mapping->attribute_count; ++ for (i = 0; i < num_attrs; ++i) { ++ if (!strcmp(attrs[i].attr.attr.name, attr->attr.name)) ++ return &attrs[i]; ++ } ++ ++ dev_err(device, "Unable to find match for device_attribute %s\n", ++ attr->attr.name); ++ return NULL; ++} ++EXPORT_SYMBOL(gasket_sysfs_get_attr); ++ ++void gasket_sysfs_put_attr(struct device *device, ++ struct gasket_sysfs_attribute *attr) ++{ ++ int i; ++ int num_attrs; ++ struct gasket_sysfs_mapping *mapping = get_mapping(device); ++ struct gasket_sysfs_attribute *attrs = NULL; ++ ++ if (!mapping) ++ return; ++ ++ attrs = mapping->attributes; ++ num_attrs = mapping->attribute_count; ++ for (i = 0; i < num_attrs; ++i) { ++ if (&attrs[i] == attr) { ++ put_mapping_n(mapping, 2); ++ return; ++ } ++ } ++ ++ dev_err(device, "Unable to put unknown attribute: %s\n", ++ attr->attr.attr.name); ++} ++EXPORT_SYMBOL(gasket_sysfs_put_attr); ++ ++ssize_t gasket_sysfs_register_store(struct device *device, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ ulong parsed_value = 0; ++ struct gasket_sysfs_mapping *mapping; ++ struct gasket_dev *gasket_dev; ++ struct gasket_sysfs_attribute *gasket_attr; ++ ++ if (count < 3 || buf[0] != '0' || buf[1] != 'x') { ++ dev_err(device, ++ "sysfs register write format: \"0x\"\n"); ++ return -EINVAL; ++ } ++ ++ if (kstrtoul(buf, 16, &parsed_value) != 0) { ++ dev_err(device, ++ "Unable to parse input as 64-bit hex value: %s\n", buf); ++ return -EINVAL; ++ } ++ ++ mapping = get_mapping(device); ++ if (!mapping) { ++ dev_err(device, "Device driver may have been removed\n"); ++ return 0; ++ } ++ ++ gasket_dev = mapping->gasket_dev; ++ if (!gasket_dev) { ++ dev_err(device, "Device driver may have been removed\n"); ++ return 0; ++ } ++ ++ gasket_attr = gasket_sysfs_get_attr(device, attr); ++ if (!gasket_attr) { ++ put_mapping(mapping); ++ return count; ++ } ++ ++ gasket_dev_write_64(gasket_dev, parsed_value, ++ gasket_attr->data.bar_address.bar, ++ gasket_attr->data.bar_address.offset); ++ ++ if (gasket_attr->write_callback) ++ gasket_attr->write_callback(gasket_dev, gasket_attr, ++ parsed_value); ++ ++ gasket_sysfs_put_attr(device, gasket_attr); ++ put_mapping(mapping); ++ return count; ++} ++EXPORT_SYMBOL(gasket_sysfs_register_store); +diff '--color=auto' -urN a/drivers/staging/gasket-driver/gasket_sysfs.h b/drivers/staging/gasket-driver/gasket_sysfs.h +--- a/drivers/staging/gasket-driver/gasket_sysfs.h 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/gasket_sysfs.h 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,186 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Set of common sysfs utilities. ++ * ++ * Copyright (C) 2018 Google, Inc. ++ */ ++ ++/* The functions described here are a set of utilities to allow each file in the ++ * Gasket driver framework to manage their own set of sysfs entries, instead of ++ * centralizing all that work in one file. ++ * ++ * The goal of these utilities is to allow for sysfs entries to be easily ++ * created without causing a proliferation of sysfs "show" functions. This ++ * requires O(N) string lookups during show function execution, but as reading ++ * sysfs entries is rarely performance-critical, this is likely acceptible. ++ */ ++#ifndef __GASKET_SYSFS_H__ ++#define __GASKET_SYSFS_H__ ++ ++#include "gasket_constants.h" ++#include "gasket_core.h" ++#include ++#include ++#include ++ ++/* The maximum number of mappings/devices a driver needs to support. */ ++#define GASKET_SYSFS_NUM_MAPPINGS (GASKET_FRAMEWORK_DESC_MAX * GASKET_DEV_MAX) ++ ++/* The maximum number of sysfs nodes in a directory. ++ */ ++#define GASKET_SYSFS_MAX_NODES 196 ++ ++/* End markers for sysfs struct arrays. */ ++#define GASKET_ARRAY_END_TOKEN GASKET_RESERVED_ARRAY_END ++#define GASKET_ARRAY_END_MARKER __stringify(GASKET_ARRAY_END_TOKEN) ++ ++/* ++ * Terminator struct for a gasket_sysfs_attr array. Must be at the end of ++ * all gasket_sysfs_attribute arrays. ++ */ ++#define GASKET_END_OF_ATTR_ARRAY \ ++ { \ ++ .attr = __ATTR(GASKET_ARRAY_END_TOKEN, S_IRUGO, NULL, NULL), \ ++ .data.attr_type = 0, \ ++ } ++ ++/* ++ * Pairing of sysfs attribute and user data. ++ * Used in lookups in sysfs "show" functions to return attribute metadata. ++ */ ++struct gasket_sysfs_attribute { ++ /* The underlying sysfs device attribute associated with this data. */ ++ struct device_attribute attr; ++ ++ /* User-specified data to associate with the attribute. */ ++ union { ++ struct bar_address_ { ++ ulong bar; ++ ulong offset; ++ } bar_address; ++ uint attr_type; ++ } data; ++ ++ /* ++ * Function pointer to a callback to be invoked when this attribute is ++ * written (if so configured). The arguments are to the Gasket device ++ * pointer, the enclosing gasket_attr structure, and the value written. ++ * The callback should perform any logging necessary, as errors cannot ++ * be returned from the callback. ++ */ ++ void (*write_callback)(struct gasket_dev *dev, ++ struct gasket_sysfs_attribute *attr, ++ ulong value); ++}; ++ ++#define GASKET_SYSFS_RO(_name, _show_function, _attr_type) \ ++ { \ ++ .attr = __ATTR(_name, S_IRUGO, _show_function, NULL), \ ++ .data.attr_type = _attr_type \ ++ } ++ ++#define GASKET_SYSFS_RW(_name, _show_function, _store_function, _attr_type) \ ++ { \ ++ .attr = __ATTR(_name, S_IWUSR | S_IWGRP | S_IRUGO, \ ++ _show_function, _store_function), \ ++ .data.attr_type = _attr_type \ ++ } ++ ++/* Initializes the Gasket sysfs subsystem. ++ * ++ * Description: Performs one-time initialization. Must be called before usage ++ * at [Gasket] module load time. ++ */ ++void gasket_sysfs_init(void); ++ ++/* ++ * Create an entry in mapping_data between a device and a Gasket device. ++ * @device: Device struct to map to. ++ * @gasket_dev: The dev struct associated with the driver controlling @device. ++ * ++ * Description: This function maps a gasket_dev* to a device*. This mapping can ++ * be used in sysfs_show functions to get a handle to the gasket_dev struct ++ * controlling the device node. ++ * ++ * If this function is not called before gasket_sysfs_create_entries, a warning ++ * will be logged. ++ */ ++int gasket_sysfs_create_mapping(struct device *device, ++ struct gasket_dev *gasket_dev); ++ ++/* ++ * Creates bulk entries in sysfs. ++ * @device: Kernel device structure. ++ * @attrs: List of attributes/sysfs entries to create. ++ * ++ * Description: Creates each sysfs entry described in "attrs". Can be called ++ * multiple times for a given @device. If the gasket_dev specified in ++ * gasket_sysfs_create_mapping had a legacy device, the entries will be created ++ * for it, as well. ++ */ ++int gasket_sysfs_create_entries(struct device *device, ++ const struct gasket_sysfs_attribute *attrs); ++ ++/* ++ * Removes a device mapping from the global table. ++ * @device: Device to unmap. ++ * ++ * Description: Removes the device->Gasket device mapping from the internal ++ * table. ++ */ ++void gasket_sysfs_remove_mapping(struct device *device); ++ ++/* ++ * User data lookup based on kernel device structure. ++ * @device: Kernel device structure. ++ * ++ * Description: Returns the user data associated with "device" in a prior call ++ * to gasket_sysfs_create_entries. Returns NULL if no mapping can be found. ++ * Upon success, this call take a reference to internal sysfs data that must be ++ * released with gasket_sysfs_put_device_data. While this reference is held, the ++ * underlying device sysfs information/structure will remain valid/will not be ++ * deleted. ++ */ ++struct gasket_dev *gasket_sysfs_get_device_data(struct device *device); ++ ++/* ++ * Releases a references to internal data. ++ * @device: Kernel device structure. ++ * @dev: Gasket device descriptor (returned by gasket_sysfs_get_device_data). ++ */ ++void gasket_sysfs_put_device_data(struct device *device, ++ struct gasket_dev *gasket_dev); ++ ++/* ++ * Gasket-specific attribute lookup. ++ * @device: Kernel device structure. ++ * @attr: Device attribute to look up. ++ * ++ * Returns the Gasket sysfs attribute associated with the kernel device ++ * attribute and device structure itself. Upon success, this call will take a ++ * reference to internal sysfs data that must be released with a call to ++ * gasket_sysfs_put_attr. While this reference is held, the underlying device ++ * sysfs information/structure will remain valid/will not be deleted. ++ */ ++struct gasket_sysfs_attribute * ++gasket_sysfs_get_attr(struct device *device, struct device_attribute *attr); ++ ++/* ++ * Releases a references to internal data. ++ * @device: Kernel device structure. ++ * @attr: Gasket sysfs attribute descriptor (returned by ++ * gasket_sysfs_get_attr). ++ */ ++void gasket_sysfs_put_attr(struct device *device, ++ struct gasket_sysfs_attribute *attr); ++ ++/* ++ * Write to a register sysfs node. ++ * @buf: NULL-terminated data being written. ++ * @count: number of bytes in the "buf" argument. ++ */ ++ssize_t gasket_sysfs_register_store(struct device *device, ++ struct device_attribute *attr, ++ const char *buf, size_t count); ++ ++#endif /* __GASKET_SYSFS_H__ */ +diff '--color=auto' -urN a/drivers/staging/gasket-driver/Kconfig b/drivers/staging/gasket-driver/Kconfig +--- a/drivers/staging/gasket-driver/Kconfig 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/Kconfig 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,24 @@ ++menu "Gasket devices" ++ ++config STAGING_GASKET_FRAMEWORK ++ tristate "Gasket framework" ++ depends on PCI && (X86_64 || ARM64) ++ help ++ This framework supports Gasket-compatible devices, such as Apex. ++ It is required for any of the following module(s). ++ ++ To compile this driver as a module, choose M here. The module ++ will be called "gasket". ++ ++config STAGING_APEX_DRIVER ++ tristate "Apex Driver" ++ depends on STAGING_GASKET_FRAMEWORK ++ help ++ This driver supports the Apex Edge TPU device. See ++ https://cloud.google.com/edge-tpu/ for more information. ++ Say Y if you want to include this driver in the kernel. ++ ++ To compile this driver as a module, choose M here. The module ++ will be called "apex". ++ ++endmenu +diff '--color=auto' -urN a/drivers/staging/gasket-driver/Makefile b/drivers/staging/gasket-driver/Makefile +--- a/drivers/staging/gasket-driver/Makefile 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/Makefile 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,17 @@ ++# ++# Makefile for Gasket framework and dependent drivers. ++# ++ ++obj-m += gasket.o ++obj-m += apex.o ++ ++gasket-objs := gasket_core.o gasket_ioctl.o gasket_interrupt.o gasket_page_table.o gasket_sysfs.o ++apex-objs := apex_driver.o ++ ++KVERSION := $(shell uname -r) ++ ++all: ++ $(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) modules ++ ++clean: ++ $(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) clean +diff '--color=auto' -urN a/drivers/staging/gasket-driver/TODO b/drivers/staging/gasket-driver/TODO +--- a/drivers/staging/gasket-driver/TODO 1970-01-01 01:00:00.000000000 +0100 ++++ b/drivers/staging/gasket-driver/TODO 2025-02-19 19:25:02.480079055 +0000 +@@ -0,0 +1,9 @@ ++This is a list of things that need to be done to get this driver out of the ++staging directory. ++- Document sysfs files with Documentation/ABI/ entries. ++- Use misc interface instead of major number for driver version description. ++- Add descriptions of module_param's ++- apex_get_status() should actually check status. ++- "drivers" should never be dealing with "raw" sysfs calls or mess around with ++ kobjects at all. The driver core should handle all of this for you ++ automaically. There should not be a need for raw attribute macros. +diff '--color=auto' -urN a/drivers/staging/Kconfig b/drivers/staging/Kconfig +--- a/drivers/staging/Kconfig 2025-02-18 23:57:39.206607973 +0000 ++++ b/drivers/staging/Kconfig 2025-02-19 19:25:43.186155250 +0000 +@@ -50,4 +50,6 @@ + + source "drivers/staging/gpib/Kconfig" + ++source "drivers/staging/gasket-driver/Kconfig" ++ + endif # STAGING diff --git a/sys-kernel/raspberrypi-sources/raspberrypi-sources-6.6.74_p20250127.ebuild b/sys-kernel/raspberrypi-sources/raspberrypi-sources-6.6.74_p20250127.ebuild new file mode 100644 index 0000000..dd619af --- /dev/null +++ b/sys-kernel/raspberrypi-sources/raspberrypi-sources-6.6.74_p20250127.ebuild @@ -0,0 +1,83 @@ +# Copyright 1999-2024 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +ETYPE=sources +K_DEFCONFIG="bcmrpi_defconfig" +K_SECURITY_UNSUPPORTED=1 +EXTRAVERSION="-${PN}/-*" + +K_EXP_GENPATCHES_NOUSE=1 +K_GENPATCHES_VER=54 +K_DEBLOB_AVAILABLE=0 +K_WANT_GENPATCHES="base extras" + +inherit kernel-2 linux-info +detect_version +detect_arch + +MY_P=$(ver_cut 4-) +MY_P="stable_${MY_P/p/}" +#COMMIT=789af262fb268b2006099a096faf4a383592ce1b +#MY_P=${COMMIT} + +DESCRIPTION="Raspberry Pi kernel sources" +HOMEPAGE="https://github.com/raspberrypi/linux" +SRC_URI=" + https://github.com/raspberrypi/linux/archive/${MY_P}.tar.gz -> linux-${KV_FULL}.tar.gz + ${GENPATCHES_URI} +" + +KEYWORDS="arm arm64" + +PATCHES=( "${FILESDIR}"/${PN}-6.1.21-gentoo-kconfig.patch + "${FILESDIR}"/raspberrypi-sources-AI.patch) + +UNIPATCH_EXCLUDE=" + 10* + 15* + 1700 + 1730 + 2000 + 201* + 2700 + 2800 + 29* + 3000 + 4567" + +pkg_setup() { + ewarn "" + ewarn "${PN} is *not* supported by the Gentoo Kernel Project in any way." + ewarn "If you need support, please contact the raspberrypi developers directly." + ewarn "Do *not* open bugs in Gentoo's bugzilla unless you have issues with" + ewarn "the ebuilds. Thank you." + ewarn "" + + kernel-2_pkg_setup +} + +universal_unpack() { + unpack linux-${KV_FULL}.tar.gz + + # We want to rename the unpacked directory to a nice normalised string + # bug #762766 + mv "${WORKDIR}"/linux-${MY_P} "${WORKDIR}"/linux-${KV_FULL} || die + + # remove all backup files + find . -iname "*~" -exec rm {} \; 2>/dev/null +} + +src_prepare() { + default + kernel-2_src_prepare +} + +pkg_postinst() { + kernel-2_pkg_postinst +} + +pkg_postrm() { + kernel-2_pkg_postrm +}