| From 8db3072225e852c2ef8bcc6c95f5b22f05104f35 Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Arve=20Hj=C3=B8nnev=C3=A5g?= <arve@android.com> |
| Date: Mon, 18 Nov 2013 20:46:48 -0800 |
| Subject: [PATCH 27/40] ANDROID: trusty: Backport of trusty driver |
| |
| This adds Trusty driver from android-trusty-5.10 |
| |
| Original commits: |
| b60d55f33484 ANDROID: trusty-ipc: Allow registering multiple handles |
| 629a4d3318cc ANDROID: trusty: Support setting trusty_shared_mem_id_t |
| 94a36a1374e7 ANDROID: trusty-log: Don't copy Trusty logs to linux kernel log |
| efc21cced8af ANDROID: trusty-log: rework buffer allocation |
| 8cb1a07ca814 ANDROID: trusty-ipc: Fix lock protection of shared_handles |
| 52cdd137fae0 ANDROID: trusty-log: support poll() |
| 24c3649dceb9 ANDROID: trusty-irq: enqueue work in trusty_irq_cpu_up |
| 05a05bdd921e ANDROID: trusty: Add config TRUSTY_CRASH_IS_PANIC |
| b5fbdba2ec72 ANDROID: trusty-ipc: Fix crash when running out of txbuffers |
| 46da5b95605e ANDROID: trusty: Allow TRUSTY_LEND of buffers |
| 2ebfb16645af ANDROID: trusty-virtio: remove unnecessary include of dma-mapping.h |
| bf9d994a65a2 ANDROID: trusty-log: Complement logging sink with unthrottled virtual file |
| d5cb51d0365d ANDROID: trusty-log: Refactor logging state to support concurrent sinks |
| b421a5ad3eb3 ANDROID: trusty-log: Sanitize u32 overflow of the log ring buffer write index |
| 58e9681c57af ANDROID: trusty-log: On trusty panic, unthrottle sink to the kernel log |
| ba12be0f203a ANDROID: trusty-log: Update trusty log buffer size to hold a complete Trusty crash logs |
| a8a3f83e52b6 ANDROID: trusty_qemu_defconfig: Enable dma-buf and ion system heaps |
| 988b52b392a1 ANDROID: trusty: Support setting FF-A Tag |
| f544e96489aa ANDROID: Add trusty_qemu_defconfig |
| 8a9b09317f29 ANDROID: trusty-ipc: Switch from memfd to dma_buf |
| 5460418ec9a4 ANDROID: trusty-irq: document new way of specifying IPIs |
| da3c30b943c2 ANDROID: trusty-irq: specify IPIs in new way |
| 5b5bb7f74856 ANDROID: trusty: Add trusty-test driver |
| e80d87f422fd ANDROID: trusty: Add trusty-ipc driver |
| 03c248cbf693 ANDROID: trusty: Add trusty-virtio driver |
| 1047661edb97 ANDROID: trusty: Add trusty-log driver |
| 18fd5c59b423 ANDROID: trusty: Add trusty-irq driver |
| 479c39a683f8 ANDROID: trusty: Add trusty-core driver |
| |
| Upstream-Status: Backport |
| Change-Id: I91f71b891a1091383a298e7fb2f9030382a19ca5 |
| Signed-off-by: Arunachalam Ganapathy <arunachalam.ganapathy@arm.com> |
| Signed-off-by: Rupinderjit Singh <rupinderjit.singh@arm.com> |
| --- |
| .../devicetree/bindings/trusty/trusty-irq.txt | 67 + |
| .../devicetree/bindings/trusty/trusty-smc.txt | 6 + |
| arch/arm/configs/trusty_qemu_defconfig | 291 +++ |
| .../configs/trusty_qemu_defconfig.fragment | 26 + |
| drivers/Kconfig | 2 + |
| drivers/Makefile | 1 + |
| drivers/trusty/Kconfig | 116 + |
| drivers/trusty/Makefile | 14 + |
| drivers/trusty/trusty-ipc.c | 2256 +++++++++++++++++ |
| drivers/trusty/trusty-irq.c | 645 +++++ |
| drivers/trusty/trusty-log.c | 830 ++++++ |
| drivers/trusty/trusty-log.h | 28 + |
| drivers/trusty/trusty-mem.c | 139 + |
| drivers/trusty/trusty-smc-arm.S | 41 + |
| drivers/trusty/trusty-smc-arm64.S | 35 + |
| drivers/trusty/trusty-smc.h | 26 + |
| drivers/trusty/trusty-test.c | 440 ++++ |
| drivers/trusty/trusty-test.h | 13 + |
| drivers/trusty/trusty-virtio.c | 840 ++++++ |
| drivers/trusty/trusty.c | 981 +++++++ |
| include/linux/trusty/arm_ffa.h | 590 +++++ |
| include/linux/trusty/sm_err.h | 28 + |
| include/linux/trusty/smcall.h | 124 + |
| include/linux/trusty/trusty.h | 131 + |
| include/linux/trusty/trusty_ipc.h | 89 + |
| include/uapi/linux/trusty/ipc.h | 65 + |
| include/uapi/linux/virtio_ids.h | 1 + |
| 27 files changed, 7825 insertions(+) |
| create mode 100644 Documentation/devicetree/bindings/trusty/trusty-irq.txt |
| create mode 100644 Documentation/devicetree/bindings/trusty/trusty-smc.txt |
| create mode 100644 arch/arm/configs/trusty_qemu_defconfig |
| create mode 100644 arch/arm64/configs/trusty_qemu_defconfig.fragment |
| create mode 100644 drivers/trusty/Kconfig |
| create mode 100644 drivers/trusty/Makefile |
| create mode 100644 drivers/trusty/trusty-ipc.c |
| create mode 100644 drivers/trusty/trusty-irq.c |
| create mode 100644 drivers/trusty/trusty-log.c |
| create mode 100644 drivers/trusty/trusty-log.h |
| create mode 100644 drivers/trusty/trusty-mem.c |
| create mode 100644 drivers/trusty/trusty-smc-arm.S |
| create mode 100644 drivers/trusty/trusty-smc-arm64.S |
| create mode 100644 drivers/trusty/trusty-smc.h |
| create mode 100644 drivers/trusty/trusty-test.c |
| create mode 100644 drivers/trusty/trusty-test.h |
| create mode 100644 drivers/trusty/trusty-virtio.c |
| create mode 100644 drivers/trusty/trusty.c |
| create mode 100644 include/linux/trusty/arm_ffa.h |
| create mode 100644 include/linux/trusty/sm_err.h |
| create mode 100644 include/linux/trusty/smcall.h |
| create mode 100644 include/linux/trusty/trusty.h |
| create mode 100644 include/linux/trusty/trusty_ipc.h |
| create mode 100644 include/uapi/linux/trusty/ipc.h |
| |
| diff --git a/Documentation/devicetree/bindings/trusty/trusty-irq.txt b/Documentation/devicetree/bindings/trusty/trusty-irq.txt |
| new file mode 100644 |
| index 000000000000..cbb545ad452b |
| --- /dev/null |
| +++ b/Documentation/devicetree/bindings/trusty/trusty-irq.txt |
| @@ -0,0 +1,67 @@ |
| +Trusty irq interface |
| + |
| +Trusty requires non-secure irqs to be forwarded to the secure OS. |
| + |
| +Required properties: |
| +- compatible: "android,trusty-irq-v1" |
| + |
| +Optional properties: |
| + |
| +- interrupt-templates: is an optional property that works together |
| + with "interrupt-ranges" to specify secure side to kernel IRQs mapping. |
| + |
| + It is a list of entries, each one of which defines a group of interrupts |
| + having common properties, and has the following format: |
| + < phandle irq_id_pos [templ_data]> |
| + phandle - phandle of interrupt controller this template is for |
| + irq_id_pos - the position of irq id in interrupt specifier array |
| + for interrupt controller referenced by phandle. |
| + templ_data - is an array of u32 values (could be empty) in the same |
| + format as interrupt specifier for interrupt controller |
| + referenced by phandle but with omitted irq id field. |
| + |
| +- interrupt-ranges: list of entries that specifies secure side to kernel |
| + IRQs mapping. |
| + |
| + Each entry in the "interrupt-ranges" list has the following format: |
| + <beg end templ_idx> |
| + beg - first entry in this range |
| + end - last entry in this range |
| + templ_idx - index of entry in "interrupt-templates" property |
| + that must be used as a template for all interrupts |
| + in this range |
| + |
| +- ipi-range: optional mapping of a linear range of trusty IRQs to a linear range |
| + of IPIs (inter-processor interrupts). This has the following format: |
| + <beg end ipi_base> |
| + beg - first trusty IRQ number that is an IPI |
| + end - last trusty IRQ number that is an IPI |
| + ipi_base - IPI number of 'beg' |
| + |
| +Example: |
| +{ |
| + gic: interrupt-controller@50041000 { |
| + compatible = "arm,gic-400"; |
| + #interrupt-cells = <3>; |
| + interrupt-controller; |
| + ... |
| + }; |
| + ... |
| + trusty { |
| + compatible = "android,trusty-smc-v1"; |
| + ranges; |
| + #address-cells = <2>; |
| + #size-cells = <2>; |
| + |
| + irq { |
| + compatible = "android,trusty-irq-v1"; |
| + interrupt-templates = <&gic 1 GIC_PPI 0>, |
| + <&gic 1 GIC_SPI 0>; |
| + interrupt-ranges = <16 31 0>, |
| + <32 223 1>; |
| + ipi-range = <8 15 8>; |
| + }; |
| + } |
| +} |
| + |
| +Must be a child of the node that provides the trusty std/fast call interface. |
| diff --git a/Documentation/devicetree/bindings/trusty/trusty-smc.txt b/Documentation/devicetree/bindings/trusty/trusty-smc.txt |
| new file mode 100644 |
| index 000000000000..1b39ad317c67 |
| --- /dev/null |
| +++ b/Documentation/devicetree/bindings/trusty/trusty-smc.txt |
| @@ -0,0 +1,6 @@ |
| +Trusty smc interface |
| + |
| +Trusty is running in secure mode on the same (arm) cpu(s) as the current os. |
| + |
| +Required properties: |
| +- compatible: "android,trusty-smc-v1" |
| diff --git a/arch/arm/configs/trusty_qemu_defconfig b/arch/arm/configs/trusty_qemu_defconfig |
| new file mode 100644 |
| index 000000000000..46ad9504c23d |
| --- /dev/null |
| +++ b/arch/arm/configs/trusty_qemu_defconfig |
| @@ -0,0 +1,291 @@ |
| +# CONFIG_LOCALVERSION_AUTO is not set |
| +# CONFIG_SWAP is not set |
| +CONFIG_POSIX_MQUEUE=y |
| +CONFIG_AUDIT=y |
| +CONFIG_NO_HZ=y |
| +CONFIG_HIGH_RES_TIMERS=y |
| +CONFIG_PREEMPT=y |
| +CONFIG_BSD_PROCESS_ACCT=y |
| +CONFIG_BSD_PROCESS_ACCT_V3=y |
| +CONFIG_TASKSTATS=y |
| +CONFIG_TASK_DELAY_ACCT=y |
| +CONFIG_TASK_XACCT=y |
| +CONFIG_TASK_IO_ACCOUNTING=y |
| +CONFIG_IKCONFIG=y |
| +CONFIG_IKCONFIG_PROC=y |
| +CONFIG_LOG_BUF_SHIFT=14 |
| +CONFIG_RT_GROUP_SCHED=y |
| +CONFIG_CGROUP_FREEZER=y |
| +CONFIG_CGROUP_CPUACCT=y |
| +CONFIG_CGROUP_DEBUG=y |
| +CONFIG_SCHED_AUTOGROUP=y |
| +CONFIG_BLK_DEV_INITRD=y |
| +CONFIG_KALLSYMS_ALL=y |
| +CONFIG_EMBEDDED=y |
| +# CONFIG_COMPAT_BRK is not set |
| +CONFIG_PROFILING=y |
| +CONFIG_ARCH_VIRT=y |
| +CONFIG_PCI=y |
| +CONFIG_PCI_HOST_GENERIC=y |
| +CONFIG_SMP=y |
| +CONFIG_HIGHMEM=y |
| +CONFIG_SECCOMP=y |
| +CONFIG_CMDLINE="console=ttyAMA0" |
| +CONFIG_PM_AUTOSLEEP=y |
| +CONFIG_PM_WAKELOCKS=y |
| +CONFIG_PM_WAKELOCKS_LIMIT=0 |
| +# CONFIG_PM_WAKELOCKS_GC is not set |
| +CONFIG_PM_DEBUG=y |
| +# CONFIG_BLK_DEV_BSG is not set |
| +# CONFIG_IOSCHED_DEADLINE is not set |
| +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set |
| +CONFIG_KSM=y |
| +CONFIG_NET=y |
| +CONFIG_PACKET=y |
| +CONFIG_UNIX=y |
| +CONFIG_XFRM_USER=y |
| +CONFIG_NET_KEY=y |
| +CONFIG_INET=y |
| +CONFIG_IP_MULTICAST=y |
| +CONFIG_IP_ADVANCED_ROUTER=y |
| +CONFIG_IP_MULTIPLE_TABLES=y |
| +CONFIG_IP_PNP=y |
| +CONFIG_IP_PNP_DHCP=y |
| +CONFIG_IP_PNP_BOOTP=y |
| +CONFIG_INET_ESP=y |
| +CONFIG_INET_DIAG_DESTROY=y |
| +CONFIG_IPV6_ROUTER_PREF=y |
| +CONFIG_IPV6_ROUTE_INFO=y |
| +CONFIG_IPV6_OPTIMISTIC_DAD=y |
| +CONFIG_INET6_AH=y |
| +CONFIG_INET6_ESP=y |
| +CONFIG_INET6_IPCOMP=y |
| +CONFIG_IPV6_MIP6=y |
| +CONFIG_IPV6_MULTIPLE_TABLES=y |
| +CONFIG_NETFILTER=y |
| +CONFIG_NF_CONNTRACK=y |
| +CONFIG_NF_CONNTRACK_SECMARK=y |
| +CONFIG_NF_CONNTRACK_EVENTS=y |
| +CONFIG_NF_CONNTRACK_AMANDA=y |
| +CONFIG_NF_CONNTRACK_FTP=y |
| +CONFIG_NF_CONNTRACK_H323=y |
| +CONFIG_NF_CONNTRACK_IRC=y |
| +CONFIG_NF_CONNTRACK_NETBIOS_NS=y |
| +CONFIG_NF_CONNTRACK_PPTP=y |
| +CONFIG_NF_CONNTRACK_SANE=y |
| +CONFIG_NF_CONNTRACK_TFTP=y |
| +CONFIG_NF_CT_NETLINK=y |
| +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y |
| +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y |
| +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y |
| +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=y |
| +CONFIG_NETFILTER_XT_TARGET_MARK=y |
| +CONFIG_NETFILTER_XT_TARGET_NFLOG=y |
| +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y |
| +CONFIG_NETFILTER_XT_TARGET_TPROXY=y |
| +CONFIG_NETFILTER_XT_TARGET_TRACE=y |
| +CONFIG_NETFILTER_XT_TARGET_SECMARK=y |
| +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y |
| +CONFIG_NETFILTER_XT_MATCH_COMMENT=y |
| +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y |
| +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y |
| +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y |
| +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y |
| +CONFIG_NETFILTER_XT_MATCH_HELPER=y |
| +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y |
| +CONFIG_NETFILTER_XT_MATCH_LENGTH=y |
| +CONFIG_NETFILTER_XT_MATCH_LIMIT=y |
| +CONFIG_NETFILTER_XT_MATCH_MAC=y |
| +CONFIG_NETFILTER_XT_MATCH_MARK=y |
| +CONFIG_NETFILTER_XT_MATCH_POLICY=y |
| +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y |
| +CONFIG_NETFILTER_XT_MATCH_QUOTA=y |
| +CONFIG_NETFILTER_XT_MATCH_QUOTA2=y |
| +CONFIG_NETFILTER_XT_MATCH_SOCKET=y |
| +CONFIG_NETFILTER_XT_MATCH_STATE=y |
| +CONFIG_NETFILTER_XT_MATCH_STATISTIC=y |
| +CONFIG_NETFILTER_XT_MATCH_STRING=y |
| +CONFIG_NETFILTER_XT_MATCH_TIME=y |
| +CONFIG_NETFILTER_XT_MATCH_U32=y |
| +CONFIG_IP_NF_IPTABLES=y |
| +CONFIG_IP_NF_MATCH_AH=y |
| +CONFIG_IP_NF_MATCH_ECN=y |
| +CONFIG_IP_NF_MATCH_RPFILTER=y |
| +CONFIG_IP_NF_MATCH_TTL=y |
| +CONFIG_IP_NF_FILTER=y |
| +CONFIG_IP_NF_TARGET_REJECT=y |
| +CONFIG_IP_NF_MANGLE=y |
| +CONFIG_IP_NF_TARGET_ECN=y |
| +CONFIG_IP_NF_TARGET_TTL=y |
| +CONFIG_IP_NF_RAW=y |
| +CONFIG_IP_NF_SECURITY=y |
| +CONFIG_IP_NF_ARPTABLES=y |
| +CONFIG_IP_NF_ARPFILTER=y |
| +CONFIG_IP_NF_ARP_MANGLE=y |
| +CONFIG_IP6_NF_IPTABLES=y |
| +CONFIG_IP6_NF_MATCH_AH=y |
| +CONFIG_IP6_NF_MATCH_EUI64=y |
| +CONFIG_IP6_NF_MATCH_FRAG=y |
| +CONFIG_IP6_NF_MATCH_OPTS=y |
| +CONFIG_IP6_NF_MATCH_HL=y |
| +CONFIG_IP6_NF_MATCH_IPV6HEADER=y |
| +CONFIG_IP6_NF_MATCH_MH=y |
| +CONFIG_IP6_NF_MATCH_RT=y |
| +CONFIG_IP6_NF_TARGET_HL=y |
| +CONFIG_IP6_NF_FILTER=y |
| +CONFIG_IP6_NF_TARGET_REJECT=y |
| +CONFIG_IP6_NF_MANGLE=y |
| +CONFIG_IP6_NF_RAW=y |
| +CONFIG_BRIDGE=y |
| +CONFIG_NET_SCHED=y |
| +CONFIG_NET_SCH_HTB=y |
| +CONFIG_NET_CLS_U32=y |
| +CONFIG_NET_EMATCH=y |
| +CONFIG_NET_EMATCH_U32=y |
| +CONFIG_NET_CLS_ACT=y |
| +# CONFIG_WIRELESS is not set |
| +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" |
| +CONFIG_BLK_DEV_LOOP=y |
| +CONFIG_BLK_DEV_RAM=y |
| +CONFIG_BLK_DEV_RAM_SIZE=8192 |
| +CONFIG_VIRTIO_BLK=y |
| +CONFIG_SCSI=y |
| +# CONFIG_SCSI_PROC_FS is not set |
| +CONFIG_BLK_DEV_SD=y |
| +# CONFIG_SCSI_LOWLEVEL is not set |
| +CONFIG_MD=y |
| +CONFIG_BLK_DEV_DM=y |
| +CONFIG_DM_CRYPT=y |
| +CONFIG_DM_UEVENT=y |
| +CONFIG_DM_VERITY=y |
| +CONFIG_DM_VERITY_FEC=y |
| +CONFIG_NETDEVICES=y |
| +CONFIG_TUN=y |
| +CONFIG_VIRTIO_NET=y |
| +CONFIG_E1000=y |
| +CONFIG_E1000E=y |
| +CONFIG_PPP=y |
| +CONFIG_PPP_BSDCOMP=y |
| +CONFIG_PPP_DEFLATE=y |
| +CONFIG_PPP_MPPE=y |
| +# CONFIG_WLAN is not set |
| +CONFIG_INPUT_EVDEV=y |
| +CONFIG_KEYBOARD_GOLDFISH_EVENTS=y |
| +# CONFIG_INPUT_MOUSE is not set |
| +CONFIG_INPUT_JOYSTICK=y |
| +CONFIG_INPUT_TABLET=y |
| +CONFIG_INPUT_MISC=y |
| +CONFIG_INPUT_UINPUT=y |
| +# CONFIG_SERIO_SERPORT is not set |
| +# CONFIG_VT is not set |
| +# CONFIG_LEGACY_PTYS is not set |
| +# CONFIG_DEVMEM is not set |
| +CONFIG_SERIAL_AMBA_PL011=y |
| +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y |
| +CONFIG_VIRTIO_CONSOLE=y |
| +# CONFIG_HW_RANDOM is not set |
| +CONFIG_BATTERY_GOLDFISH=y |
| +# CONFIG_HWMON is not set |
| +CONFIG_TRUSTY=y |
| +CONFIG_MEDIA_SUPPORT=y |
| +CONFIG_FB=y |
| +CONFIG_FB_GOLDFISH=y |
| +CONFIG_FB_SIMPLE=y |
| +CONFIG_BACKLIGHT_LCD_SUPPORT=y |
| +CONFIG_LOGO=y |
| +# CONFIG_LOGO_LINUX_MONO is not set |
| +# CONFIG_LOGO_LINUX_VGA16 is not set |
| +CONFIG_SOUND=y |
| +CONFIG_SND=y |
| +CONFIG_HIDRAW=y |
| +CONFIG_UHID=y |
| +CONFIG_HID_A4TECH=y |
| +CONFIG_HID_ACRUX=y |
| +CONFIG_HID_ACRUX_FF=y |
| +CONFIG_HID_APPLE=y |
| +CONFIG_HID_BELKIN=y |
| +CONFIG_HID_CHERRY=y |
| +CONFIG_HID_CHICONY=y |
| +CONFIG_HID_PRODIKEYS=y |
| +CONFIG_HID_CYPRESS=y |
| +CONFIG_HID_DRAGONRISE=y |
| +CONFIG_DRAGONRISE_FF=y |
| +CONFIG_HID_EMS_FF=y |
| +CONFIG_HID_ELECOM=y |
| +CONFIG_HID_EZKEY=y |
| +CONFIG_HID_KEYTOUCH=y |
| +CONFIG_HID_KYE=y |
| +CONFIG_HID_WALTOP=y |
| +CONFIG_HID_GYRATION=y |
| +CONFIG_HID_TWINHAN=y |
| +CONFIG_HID_KENSINGTON=y |
| +CONFIG_HID_LCPOWER=y |
| +CONFIG_HID_LOGITECH=y |
| +CONFIG_HID_LOGITECH_DJ=y |
| +CONFIG_LOGITECH_FF=y |
| +CONFIG_LOGIRUMBLEPAD2_FF=y |
| +CONFIG_LOGIG940_FF=y |
| +CONFIG_HID_MAGICMOUSE=y |
| +CONFIG_HID_MICROSOFT=y |
| +CONFIG_HID_MONTEREY=y |
| +CONFIG_HID_MULTITOUCH=y |
| +CONFIG_HID_ORTEK=y |
| +CONFIG_HID_PANTHERLORD=y |
| +CONFIG_PANTHERLORD_FF=y |
| +CONFIG_HID_PETALYNX=y |
| +CONFIG_HID_PICOLCD=y |
| +CONFIG_HID_PRIMAX=y |
| +CONFIG_HID_SAITEK=y |
| +CONFIG_HID_SAMSUNG=y |
| +CONFIG_HID_SPEEDLINK=y |
| +CONFIG_HID_SUNPLUS=y |
| +CONFIG_HID_GREENASIA=y |
| +CONFIG_GREENASIA_FF=y |
| +CONFIG_HID_SMARTJOYPLUS=y |
| +CONFIG_SMARTJOYPLUS_FF=y |
| +CONFIG_HID_TIVO=y |
| +CONFIG_HID_TOPSEED=y |
| +CONFIG_HID_THRUSTMASTER=y |
| +CONFIG_HID_ZEROPLUS=y |
| +CONFIG_HID_ZYDACRON=y |
| +# CONFIG_USB_SUPPORT is not set |
| +CONFIG_RTC_CLASS=y |
| +CONFIG_VIRTIO_PCI=y |
| +CONFIG_VIRTIO_MMIO=y |
| +CONFIG_STAGING=y |
| +CONFIG_ASHMEM=y |
| +CONFIG_ION=y |
| +CONFIG_GOLDFISH_AUDIO=y |
| +CONFIG_GOLDFISH=y |
| +CONFIG_GOLDFISH_PIPE=y |
| +# CONFIG_IOMMU_SUPPORT is not set |
| +CONFIG_ANDROID=y |
| +CONFIG_ANDROID_BINDER_IPC=y |
| +CONFIG_EXT2_FS=y |
| +CONFIG_EXT4_FS=y |
| +CONFIG_EXT4_FS_SECURITY=y |
| +CONFIG_QUOTA=y |
| +CONFIG_FUSE_FS=y |
| +CONFIG_CUSE=y |
| +CONFIG_MSDOS_FS=y |
| +CONFIG_VFAT_FS=y |
| +CONFIG_TMPFS=y |
| +CONFIG_TMPFS_POSIX_ACL=y |
| +# CONFIG_MISC_FILESYSTEMS is not set |
| +CONFIG_NFS_FS=y |
| +CONFIG_ROOT_NFS=y |
| +CONFIG_NLS_CODEPAGE_437=y |
| +CONFIG_NLS_ISO8859_1=y |
| +CONFIG_SECURITY=y |
| +CONFIG_SECURITY_NETWORK=y |
| +CONFIG_SECURITY_SELINUX=y |
| +CONFIG_DYNAMIC_DEBUG=y |
| +CONFIG_DEBUG_INFO=y |
| +CONFIG_DEBUG_FS=y |
| +CONFIG_MAGIC_SYSRQ=y |
| +CONFIG_PANIC_TIMEOUT=5 |
| +# CONFIG_SCHED_DEBUG is not set |
| +CONFIG_SCHEDSTATS=y |
| +# CONFIG_FTRACE is not set |
| +CONFIG_DMA_API_DEBUG=y |
| +CONFIG_ATOMIC64_SELFTEST=y |
| diff --git a/arch/arm64/configs/trusty_qemu_defconfig.fragment b/arch/arm64/configs/trusty_qemu_defconfig.fragment |
| new file mode 100644 |
| index 000000000000..166eef1797fd |
| --- /dev/null |
| +++ b/arch/arm64/configs/trusty_qemu_defconfig.fragment |
| @@ -0,0 +1,26 @@ |
| +# From goldfish |
| +CONFIG_VIRTIO_BLK=y |
| +CONFIG_VIRTIO_CONSOLE=y |
| +CONFIG_VIRTIO_INPUT=y |
| +CONFIG_VIRTIO_MMIO=y |
| +CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y |
| +CONFIG_VIRTIO_NET=y |
| +CONFIG_VIRTIO_PCI=y |
| +CONFIG_VIRTIO_PMEM=y |
| +# From Trusty |
| +CONFIG_TRUSTY=y |
| +CONFIG_DMA_API_DEBUG=y |
| +CONFIG_DYNAMIC_DEBUG=y |
| +CONFIG_PROVE_LOCKING=y |
| +CONFIG_DEBUG_ATOMIC_SLEEP=y |
| +CONFIG_SEMIHOSTING_EXIT=y |
| +CONFIG_E1000=y |
| +CONFIG_E1000E=y |
| +CONFIG_REBOOT_EMULATOR_EXIT=y |
| +CONFIG_DMABUF_HEAPS_SYSTEM=y |
| +# securefb test uses ION |
| +CONFIG_ION=y |
| +CONFIG_ION_SYSTEM_HEAP=y |
| +# LTO slows down build times considerably. Disable it. |
| +# CONFIG_LTO_CLANG is not set |
| +# CONFIG_LTO_CLANG_FULL is not set |
| diff --git a/drivers/Kconfig b/drivers/Kconfig |
| index 0d399ddaa185..e346c35f42b4 100644 |
| --- a/drivers/Kconfig |
| +++ b/drivers/Kconfig |
| @@ -85,6 +85,8 @@ source "drivers/hwmon/Kconfig" |
| |
| source "drivers/thermal/Kconfig" |
| |
| +source "drivers/trusty/Kconfig" |
| + |
| source "drivers/watchdog/Kconfig" |
| |
| source "drivers/ssb/Kconfig" |
| diff --git a/drivers/Makefile b/drivers/Makefile |
| index a110338c860c..d3165b877622 100644 |
| --- a/drivers/Makefile |
| +++ b/drivers/Makefile |
| @@ -117,6 +117,7 @@ obj-$(CONFIG_W1) += w1/ |
| obj-y += power/ |
| obj-$(CONFIG_HWMON) += hwmon/ |
| obj-$(CONFIG_THERMAL) += thermal/ |
| +obj-$(CONFIG_TRUSTY) += trusty/ |
| obj-$(CONFIG_WATCHDOG) += watchdog/ |
| obj-$(CONFIG_MD) += md/ |
| obj-$(CONFIG_BT) += bluetooth/ |
| diff --git a/drivers/trusty/Kconfig b/drivers/trusty/Kconfig |
| new file mode 100644 |
| index 000000000000..fcde7f097acf |
| --- /dev/null |
| +++ b/drivers/trusty/Kconfig |
| @@ -0,0 +1,116 @@ |
| +# SPDX-License-Identifier: GPL-2.0-only |
| +# |
| +# Trusty driver |
| +# |
| + |
| +menu "Trusty driver" |
| + |
| +config TRUSTY |
| + tristate "Trusty core driver" |
| + depends on ARM || ARM64 |
| + help |
| + Trusty is a secure OS that provides a Trusted Execution Environment |
| + (TEE) for Android. Trusty runs on the same processor as Linux but is |
| + isolated from the rest of the system by both hardware and software. |
| + |
| + This option enables the core part of the Linux kernel driver for |
| + Trusty. This doesn't do much by itself; you'll need to enable some of |
| + the sub-modules too. |
| + |
| + If you build this as a module, it will be called trusty-core. |
| + |
| +if TRUSTY |
| + |
| +config TRUSTY_IRQ |
| + tristate "Trusty IRQ support" |
| + default y |
| + help |
| + Enable forwarding of IRQs from Linux to Trusty. This module retrieves |
| + from Trusty a list of IRQs that Trusty uses, and it registers handlers |
| + for them which notify Trusty that the IRQ has been received. |
| + |
| + If you build this as a module, it will be called trusty-irq. |
| + |
| + Usually this is needed for Trusty to work, so say 'y' or 'm'. |
| + |
| +config TRUSTY_LOG |
| + tristate "Trusty log support" |
| + default y |
| + help |
| + Print log messages generated by the secure OS to the Linux kernel log. |
| + |
| + While this module is loaded, messages are retrieved and printed after |
| + each call into Trusty, and also during Linux kernel panics. |
| + |
| + If you build this as a module, it will be called trusty-log. |
| + |
| +config TRUSTY_TEST |
| + tristate "Trusty stdcall test" |
| + default y |
| + help |
| + Allow running tests of the Trusty stdcall interface. Running these |
| + tests is initiated by userspace writing to a sysfs file. |
| + |
| + This depends on having a test sevice running on the Trusty side. |
| + |
| + If you build this as a module, it will be called trusty-test. |
| + |
| +config TRUSTY_VIRTIO |
| + tristate "Trusty virtio support" |
| + select VIRTIO |
| + default y |
| + help |
| + Enable the Trusty virtio driver, which is responsible for management |
| + and interaction with virtio devices exposed by Trusty. This driver |
| + requests the virtio device descriptors from Trusty, then parses them |
| + and adds the corresponding virtio devices. |
| + |
| + If you build this as a module, it will be called trusty-virtio. |
| + |
| +config TRUSTY_VIRTIO_IPC |
| + tristate "Trusty Virtio IPC driver" |
| + depends on TRUSTY_VIRTIO |
| + default y |
| + help |
| + Enable support for communicating with Trusty services. |
| + |
| + If you build this as a module, it will be called trusty-ipc. |
| + |
| +config TRUSTY_DMA_BUF_FFA_TAG |
| + bool "Availability of trusty_dma_buf_get_ffa_tag" |
| + default n |
| + help |
| + Whether trusty_dma_buf_get_ffa_tag is provided on this platform. |
| + Providing this function will allow the platform to select what tag |
| + should be passed to the SPM when attempting to transfer the buffer |
| + to secure world. The value passed here is implementation defined and |
| + may depend on your SPM. |
| + |
| + If set to N, a default implementation which returns 0 will be used. |
| + |
| +config TRUSTY_DMA_BUF_SHARED_MEM_ID |
| + bool "Availability of trusty_dma_buf_get_shared_mem_id" |
| + default n |
| + help |
| + Whether trusty_dma_buf_get_shared_mem_id is provided on this platform. |
| + Providing this function allows the platform to manage memory |
| + transaction life cycle of DMA bufs independently of Trusty IPC driver. |
| + The latter can query trusty_shared_mem_id_t value allocated for a |
| + given DMA buf using trusty_dma_buf_get_shared_mem_id interface. |
| + |
| + If set to N, a default implementation which does not allocate any IDs |
| + will be used. |
| + |
| +config TRUSTY_CRASH_IS_PANIC |
| + bool "When trusty panics, then panic the kernel" |
| + help |
| + This option will treat Trusty panics as fatal. This is useful if |
| + your system cannot recover from Trusty panic/halt and you require |
| + the system to reboot to recover. |
| + |
| + If N, it will contine to run the kernel, but trusty operations will |
| + return errors. |
| + |
| +endif # TRUSTY |
| + |
| +endmenu |
| diff --git a/drivers/trusty/Makefile b/drivers/trusty/Makefile |
| new file mode 100644 |
| index 000000000000..2cf1cfccf97b |
| --- /dev/null |
| +++ b/drivers/trusty/Makefile |
| @@ -0,0 +1,14 @@ |
| +# SPDX-License-Identifier: GPL-2.0-only |
| +# |
| +# Makefile for trusty components |
| +# |
| + |
| +obj-$(CONFIG_TRUSTY) += trusty-core.o |
| +trusty-core-objs += trusty.o trusty-mem.o |
| +trusty-core-$(CONFIG_ARM) += trusty-smc-arm.o |
| +trusty-core-$(CONFIG_ARM64) += trusty-smc-arm64.o |
| +obj-$(CONFIG_TRUSTY_IRQ) += trusty-irq.o |
| +obj-$(CONFIG_TRUSTY_LOG) += trusty-log.o |
| +obj-$(CONFIG_TRUSTY_TEST) += trusty-test.o |
| +obj-$(CONFIG_TRUSTY_VIRTIO) += trusty-virtio.o |
| +obj-$(CONFIG_TRUSTY_VIRTIO_IPC) += trusty-ipc.o |
| diff --git a/drivers/trusty/trusty-ipc.c b/drivers/trusty/trusty-ipc.c |
| new file mode 100644 |
| index 000000000000..82d6ddeb41f4 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-ipc.c |
| @@ -0,0 +1,2256 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2020 Google, Inc. |
| + */ |
| + |
| +#include <linux/aio.h> |
| +#include <linux/kernel.h> |
| +#include <linux/module.h> |
| +#include <linux/cdev.h> |
| +#include <linux/slab.h> |
| +#include <linux/fs.h> |
| +#include <linux/poll.h> |
| +#include <linux/idr.h> |
| +#include <linux/completion.h> |
| +#include <linux/dma-buf.h> |
| +#include <linux/sched.h> |
| +#include <linux/sched/signal.h> |
| +#include <linux/compat.h> |
| +#include <linux/uio.h> |
| +#include <linux/file.h> |
| + |
| +#include <linux/virtio.h> |
| +#include <linux/virtio_ids.h> |
| +#include <linux/virtio_config.h> |
| + |
| +#include <linux/trusty/trusty.h> |
| +#include <linux/trusty/trusty_ipc.h> |
| + |
| +#include <uapi/linux/trusty/ipc.h> |
| + |
| +#define MAX_DEVICES 4 |
| + |
| +#define REPLY_TIMEOUT 5000 |
| +#define TXBUF_TIMEOUT 15000 |
| + |
| +#define MAX_SRV_NAME_LEN 256 |
| +#define MAX_DEV_NAME_LEN 32 |
| + |
| +#define DEFAULT_MSG_BUF_SIZE PAGE_SIZE |
| +#define DEFAULT_MSG_BUF_ALIGN PAGE_SIZE |
| + |
| +#define TIPC_CTRL_ADDR 53 |
| +#define TIPC_ANY_ADDR 0xFFFFFFFF |
| + |
| +#define TIPC_MIN_LOCAL_ADDR 1024 |
| + |
| +#ifdef CONFIG_COMPAT |
| +#define TIPC_IOC32_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, compat_uptr_t) |
| +#endif |
| + |
| +struct tipc_virtio_dev; |
| + |
| +struct tipc_dev_config { |
| + u32 msg_buf_max_size; |
| + u32 msg_buf_alignment; |
| + char dev_name[MAX_DEV_NAME_LEN]; |
| +} __packed; |
| + |
| +struct tipc_shm { |
| + trusty_shared_mem_id_t obj_id; |
| + u64 size; |
| + u64 tag; |
| +}; |
| + |
| +struct tipc_msg_hdr { |
| + u32 src; |
| + u32 dst; |
| + u16 reserved; |
| + u16 shm_cnt; |
| + u16 len; |
| + u16 flags; |
| + u8 data[]; |
| +} __packed; |
| + |
| +enum tipc_ctrl_msg_types { |
| + TIPC_CTRL_MSGTYPE_GO_ONLINE = 1, |
| + TIPC_CTRL_MSGTYPE_GO_OFFLINE, |
| + TIPC_CTRL_MSGTYPE_CONN_REQ, |
| + TIPC_CTRL_MSGTYPE_CONN_RSP, |
| + TIPC_CTRL_MSGTYPE_DISC_REQ, |
| + TIPC_CTRL_MSGTYPE_RELEASE, |
| +}; |
| + |
| +struct tipc_ctrl_msg { |
| + u32 type; |
| + u32 body_len; |
| + u8 body[]; |
| +} __packed; |
| + |
| +struct tipc_conn_req_body { |
| + char name[MAX_SRV_NAME_LEN]; |
| +} __packed; |
| + |
| +struct tipc_conn_rsp_body { |
| + u32 target; |
| + u32 status; |
| + u32 remote; |
| + u32 max_msg_size; |
| + u32 max_msg_cnt; |
| +} __packed; |
| + |
| +struct tipc_disc_req_body { |
| + u32 target; |
| +} __packed; |
| + |
| +struct tipc_release_body { |
| + trusty_shared_mem_id_t id; |
| +} __packed; |
| + |
| +struct tipc_cdev_node { |
| + struct cdev cdev; |
| + struct device *dev; |
| + unsigned int minor; |
| +}; |
| + |
| +enum tipc_device_state { |
| + VDS_OFFLINE = 0, |
| + VDS_ONLINE, |
| + VDS_DEAD, |
| +}; |
| + |
| +struct tipc_virtio_dev { |
| + struct kref refcount; |
| + struct mutex lock; /* protects access to this device */ |
| + struct virtio_device *vdev; |
| + struct virtqueue *rxvq; |
| + struct virtqueue *txvq; |
| + unsigned int msg_buf_cnt; |
| + unsigned int msg_buf_max_cnt; |
| + size_t msg_buf_max_sz; |
| + unsigned int free_msg_buf_cnt; |
| + struct list_head free_buf_list; |
| + wait_queue_head_t sendq; |
| + struct idr addr_idr; |
| + enum tipc_device_state state; |
| + struct tipc_cdev_node cdev_node; |
| + /* protects shared_handles, dev lock never acquired while held */ |
| + struct mutex shared_handles_lock; |
| + struct rb_root shared_handles; |
| + char cdev_name[MAX_DEV_NAME_LEN]; |
| +}; |
| + |
| +enum tipc_chan_state { |
| + TIPC_DISCONNECTED = 0, |
| + TIPC_CONNECTING, |
| + TIPC_CONNECTED, |
| + TIPC_STALE, |
| +}; |
| + |
| +struct tipc_chan { |
| + struct mutex lock; /* protects channel state */ |
| + struct kref refcount; |
| + enum tipc_chan_state state; |
| + struct tipc_virtio_dev *vds; |
| + const struct tipc_chan_ops *ops; |
| + void *ops_arg; |
| + u32 remote; |
| + u32 local; |
| + u32 max_msg_size; |
| + u32 max_msg_cnt; |
| + char srv_name[MAX_SRV_NAME_LEN]; |
| +}; |
| + |
| +struct tipc_shared_handle { |
| + struct rb_node node; |
| + struct tipc_shm tipc; |
| + struct tipc_virtio_dev *vds; |
| + struct dma_buf *dma_buf; |
| + bool shared; |
| + /* |
| + * Following fields are only used if dma_buf does not own a |
| + * trusty_shared_mem_id_t. |
| + */ |
| + struct dma_buf_attachment *attach; |
| + struct sg_table *sgt; |
| +}; |
| + |
| +static struct class *tipc_class; |
| +static unsigned int tipc_major; |
| + |
| +static struct virtio_device *default_vdev; |
| + |
| +static DEFINE_IDR(tipc_devices); |
| +static DEFINE_MUTEX(tipc_devices_lock); |
| + |
| +static int _match_any(int id, void *p, void *data) |
| +{ |
| + return id; |
| +} |
| + |
| +static int _match_data(int id, void *p, void *data) |
| +{ |
| + return (p == data); |
| +} |
| + |
| +static void *_alloc_shareable_mem(size_t sz, gfp_t gfp) |
| +{ |
| + return alloc_pages_exact(sz, gfp); |
| +} |
| + |
| +static void _free_shareable_mem(size_t sz, void *va) |
| +{ |
| + free_pages_exact(va, sz); |
| +} |
| + |
| +static struct tipc_msg_buf *vds_alloc_msg_buf(struct tipc_virtio_dev *vds, |
| + bool share_write) |
| +{ |
| + int ret; |
| + struct tipc_msg_buf *mb; |
| + size_t sz = vds->msg_buf_max_sz; |
| + pgprot_t pgprot = share_write ? PAGE_KERNEL : PAGE_KERNEL_RO; |
| + |
| + /* allocate tracking structure */ |
| + mb = kzalloc(sizeof(struct tipc_msg_buf), GFP_KERNEL); |
| + if (!mb) |
| + return NULL; |
| + |
| + /* allocate buffer that can be shared with secure world */ |
| + mb->buf_va = _alloc_shareable_mem(sz, GFP_KERNEL); |
| + if (!mb->buf_va) |
| + goto err_alloc; |
| + |
| + sg_init_one(&mb->sg, mb->buf_va, sz); |
| + ret = trusty_share_memory_compat(vds->vdev->dev.parent->parent, |
| + &mb->buf_id, &mb->sg, 1, pgprot); |
| + if (ret) { |
| + dev_err(&vds->vdev->dev, "trusty_share_memory failed: %d\n", |
| + ret); |
| + goto err_share; |
| + } |
| + |
| + mb->buf_sz = sz; |
| + mb->shm_cnt = 0; |
| + |
| + return mb; |
| + |
| +err_share: |
| + _free_shareable_mem(sz, mb->buf_va); |
| +err_alloc: |
| + kfree(mb); |
| + return NULL; |
| +} |
| + |
| +static void vds_free_msg_buf(struct tipc_virtio_dev *vds, |
| + struct tipc_msg_buf *mb) |
| +{ |
| + int ret; |
| + |
| + ret = trusty_reclaim_memory(vds->vdev->dev.parent->parent, mb->buf_id, |
| + &mb->sg, 1); |
| + if (WARN_ON(ret)) { |
| + dev_err(&vds->vdev->dev, |
| + "trusty_revoke_memory failed: %d txbuf %lld\n", |
| + ret, mb->buf_id); |
| + |
| + /* |
| + * It is not safe to free this memory if trusty_revoke_memory |
| + * fails. Leak it in that case. |
| + */ |
| + } else { |
| + _free_shareable_mem(mb->buf_sz, mb->buf_va); |
| + } |
| + kfree(mb); |
| +} |
| + |
| +static void vds_free_msg_buf_list(struct tipc_virtio_dev *vds, |
| + struct list_head *list) |
| +{ |
| + struct tipc_msg_buf *mb = NULL; |
| + |
| + mb = list_first_entry_or_null(list, struct tipc_msg_buf, node); |
| + while (mb) { |
| + list_del(&mb->node); |
| + vds_free_msg_buf(vds, mb); |
| + mb = list_first_entry_or_null(list, struct tipc_msg_buf, node); |
| + } |
| +} |
| + |
| +static inline void mb_reset(struct tipc_msg_buf *mb) |
| +{ |
| + mb->wpos = 0; |
| + mb->rpos = 0; |
| +} |
| + |
| +static inline void mb_reset_read(struct tipc_msg_buf *mb) |
| +{ |
| + mb->rpos = 0; |
| +} |
| + |
| +static void _free_vds(struct kref *kref) |
| +{ |
| + struct tipc_virtio_dev *vds = |
| + container_of(kref, struct tipc_virtio_dev, refcount); |
| + /* |
| + * If this WARN triggers, we're leaking remote memory references. |
| + * |
| + * No need to lock shared_handles_lock. All references to this lock |
| + * should already be gone by this point, since we are freeing it in this |
| + * function. |
| + */ |
| + WARN_ON(!RB_EMPTY_ROOT(&vds->shared_handles)); |
| + kfree(vds); |
| +} |
| + |
| +static void _free_chan(struct kref *kref) |
| +{ |
| + struct tipc_chan *ch = container_of(kref, struct tipc_chan, refcount); |
| + |
| + if (ch->ops && ch->ops->handle_release) |
| + ch->ops->handle_release(ch->ops_arg); |
| + |
| + kref_put(&ch->vds->refcount, _free_vds); |
| + kfree(ch); |
| +} |
| + |
| +static bool _put_txbuf_locked(struct tipc_virtio_dev *vds, |
| + struct tipc_msg_buf *mb) |
| +{ |
| + list_add_tail(&mb->node, &vds->free_buf_list); |
| + return vds->free_msg_buf_cnt++ == 0; |
| +} |
| + |
| +static struct tipc_msg_buf *_get_txbuf_locked(struct tipc_virtio_dev *vds) |
| +{ |
| + struct tipc_msg_buf *mb; |
| + |
| + if (vds->state != VDS_ONLINE) |
| + return ERR_PTR(-ENODEV); |
| + |
| + if (vds->free_msg_buf_cnt) { |
| + /* take it out of free list */ |
| + mb = list_first_entry(&vds->free_buf_list, |
| + struct tipc_msg_buf, node); |
| + list_del(&mb->node); |
| + mb->shm_cnt = 0; |
| + vds->free_msg_buf_cnt--; |
| + } else { |
| + if (vds->msg_buf_cnt >= vds->msg_buf_max_cnt) |
| + return ERR_PTR(-EAGAIN); |
| + |
| + /* try to allocate it */ |
| + mb = vds_alloc_msg_buf(vds, false); |
| + if (!mb) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + vds->msg_buf_cnt++; |
| + } |
| + return mb; |
| +} |
| + |
| +static struct tipc_msg_buf *_vds_get_txbuf(struct tipc_virtio_dev *vds) |
| +{ |
| + struct tipc_msg_buf *mb; |
| + |
| + mutex_lock(&vds->lock); |
| + mb = _get_txbuf_locked(vds); |
| + mutex_unlock(&vds->lock); |
| + |
| + return mb; |
| +} |
| + |
| +static void vds_put_txbuf(struct tipc_virtio_dev *vds, struct tipc_msg_buf *mb) |
| +{ |
| + mutex_lock(&vds->lock); |
| + _put_txbuf_locked(vds, mb); |
| + wake_up_interruptible(&vds->sendq); |
| + mutex_unlock(&vds->lock); |
| +} |
| + |
| +static struct tipc_msg_buf *vds_get_txbuf(struct tipc_virtio_dev *vds, |
| + long timeout) |
| +{ |
| + struct tipc_msg_buf *mb; |
| + |
| + mb = _vds_get_txbuf(vds); |
| + |
| + if ((PTR_ERR(mb) == -EAGAIN) && timeout) { |
| + DEFINE_WAIT_FUNC(wait, woken_wake_function); |
| + |
| + timeout = msecs_to_jiffies(timeout); |
| + add_wait_queue(&vds->sendq, &wait); |
| + for (;;) { |
| + timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, |
| + timeout); |
| + if (!timeout) { |
| + mb = ERR_PTR(-ETIMEDOUT); |
| + break; |
| + } |
| + |
| + if (signal_pending(current)) { |
| + mb = ERR_PTR(-ERESTARTSYS); |
| + break; |
| + } |
| + |
| + mb = _vds_get_txbuf(vds); |
| + if (PTR_ERR(mb) != -EAGAIN) |
| + break; |
| + } |
| + remove_wait_queue(&vds->sendq, &wait); |
| + } |
| + |
| + if (IS_ERR(mb)) |
| + return mb; |
| + |
| + if (WARN_ON(!mb)) |
| + return ERR_PTR(-EINVAL); |
| + |
| + /* reset and reserve space for message header */ |
| + mb_reset(mb); |
| + mb_put_data(mb, sizeof(struct tipc_msg_hdr)); |
| + |
| + return mb; |
| +} |
| + |
| +static int vds_queue_txbuf(struct tipc_virtio_dev *vds, |
| + struct tipc_msg_buf *mb) |
| +{ |
| + int err; |
| + struct scatterlist sg; |
| + bool need_notify = false; |
| + |
| + mutex_lock(&vds->lock); |
| + if (vds->state == VDS_ONLINE) { |
| + sg_init_one(&sg, mb, mb->wpos); |
| + err = virtqueue_add_outbuf(vds->txvq, &sg, 1, mb, GFP_KERNEL); |
| + need_notify = virtqueue_kick_prepare(vds->txvq); |
| + } else { |
| + err = -ENODEV; |
| + } |
| + mutex_unlock(&vds->lock); |
| + |
| + if (need_notify) |
| + virtqueue_notify(vds->txvq); |
| + |
| + return err; |
| +} |
| + |
| +static int vds_add_channel(struct tipc_virtio_dev *vds, |
| + struct tipc_chan *chan) |
| +{ |
| + int ret; |
| + |
| + mutex_lock(&vds->lock); |
| + if (vds->state == VDS_ONLINE) { |
| + ret = idr_alloc(&vds->addr_idr, chan, |
| + TIPC_MIN_LOCAL_ADDR, TIPC_ANY_ADDR - 1, |
| + GFP_KERNEL); |
| + if (ret > 0) { |
| + chan->local = ret; |
| + kref_get(&chan->refcount); |
| + ret = 0; |
| + } |
| + } else { |
| + ret = -EINVAL; |
| + } |
| + mutex_unlock(&vds->lock); |
| + |
| + return ret; |
| +} |
| + |
| +static void vds_del_channel(struct tipc_virtio_dev *vds, |
| + struct tipc_chan *chan) |
| +{ |
| + mutex_lock(&vds->lock); |
| + if (chan->local) { |
| + idr_remove(&vds->addr_idr, chan->local); |
| + chan->local = 0; |
| + chan->remote = 0; |
| + kref_put(&chan->refcount, _free_chan); |
| + } |
| + mutex_unlock(&vds->lock); |
| +} |
| + |
| +static struct tipc_chan *vds_lookup_channel(struct tipc_virtio_dev *vds, |
| + u32 addr) |
| +{ |
| + int id; |
| + struct tipc_chan *chan = NULL; |
| + |
| + mutex_lock(&vds->lock); |
| + if (addr == TIPC_ANY_ADDR) { |
| + id = idr_for_each(&vds->addr_idr, _match_any, NULL); |
| + if (id > 0) |
| + chan = idr_find(&vds->addr_idr, id); |
| + } else { |
| + chan = idr_find(&vds->addr_idr, addr); |
| + } |
| + if (chan) |
| + kref_get(&chan->refcount); |
| + mutex_unlock(&vds->lock); |
| + |
| + return chan; |
| +} |
| + |
| +static struct tipc_chan *vds_create_channel(struct tipc_virtio_dev *vds, |
| + const struct tipc_chan_ops *ops, |
| + void *ops_arg) |
| +{ |
| + int ret; |
| + struct tipc_chan *chan = NULL; |
| + |
| + if (!vds) |
| + return ERR_PTR(-ENOENT); |
| + |
| + if (!ops) |
| + return ERR_PTR(-EINVAL); |
| + |
| + chan = kzalloc(sizeof(*chan), GFP_KERNEL); |
| + if (!chan) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + kref_get(&vds->refcount); |
| + chan->vds = vds; |
| + chan->ops = ops; |
| + chan->ops_arg = ops_arg; |
| + mutex_init(&chan->lock); |
| + kref_init(&chan->refcount); |
| + chan->state = TIPC_DISCONNECTED; |
| + |
| + ret = vds_add_channel(vds, chan); |
| + if (ret) { |
| + kfree(chan); |
| + kref_put(&vds->refcount, _free_vds); |
| + return ERR_PTR(ret); |
| + } |
| + |
| + return chan; |
| +} |
| + |
| +static void fill_msg_hdr(struct tipc_msg_buf *mb, u32 src, u32 dst) |
| +{ |
| + struct tipc_msg_hdr *hdr = mb_get_data(mb, sizeof(*hdr)); |
| + |
| + hdr->src = src; |
| + hdr->dst = dst; |
| + hdr->len = mb_avail_data(mb); |
| + hdr->flags = 0; |
| + hdr->shm_cnt = mb->shm_cnt; |
| + hdr->reserved = 0; |
| +} |
| + |
| +static int tipc_shared_handle_new(struct tipc_shared_handle **shared_handle, |
| + struct tipc_virtio_dev *vds) |
| +{ |
| + struct tipc_shared_handle *out = kzalloc(sizeof(*out), GFP_KERNEL); |
| + |
| + if (!out) |
| + return -ENOMEM; |
| + |
| + out->vds = vds; |
| + *shared_handle = out; |
| + |
| + return 0; |
| +} |
| + |
| +static struct device *tipc_shared_handle_dev(struct tipc_shared_handle |
| + *shared_handle) |
| +{ |
| + return shared_handle->vds->vdev->dev.parent->parent; |
| +} |
| + |
| +static bool is_same_memory_region(struct tipc_shared_handle *h1, |
| + struct tipc_shared_handle *h2) |
| +{ |
| + return h1->tipc.obj_id == h2->tipc.obj_id && |
| + h1->tipc.size == h2->tipc.size && |
| + h1->tipc.tag == h2->tipc.tag && |
| + h1->dma_buf == h2->dma_buf && |
| + h1->shared == h2->shared; |
| +} |
| + |
| +static bool dma_buf_owns_shared_mem_id(struct tipc_shared_handle *h) |
| +{ |
| + /* h->shared is true only if dma_buf did not own an shared memory ID */ |
| + return !h->shared; |
| +} |
| + |
| +static void tipc_shared_handle_register(struct tipc_shared_handle |
| + *new_handle) |
| +{ |
| + struct tipc_virtio_dev *vds = new_handle->vds; |
| + struct rb_node **new; |
| + struct rb_node *parent = NULL; |
| + |
| + mutex_lock(&vds->shared_handles_lock); |
| + |
| + new = &vds->shared_handles.rb_node; |
| + while (*new) { |
| + struct tipc_shared_handle *handle = |
| + rb_entry(*new, struct tipc_shared_handle, node); |
| + parent = *new; |
| + /* |
| + * An obj_id can be registered multiple times if it's owned by a |
| + * dma_buf, because in this case we use the same obj_id across |
| + * multiple memory transfer operations. |
| + */ |
| + if (handle->tipc.obj_id == new_handle->tipc.obj_id) { |
| + if (dma_buf_owns_shared_mem_id(new_handle)) { |
| + WARN_ON(!is_same_memory_region(handle, |
| + new_handle)); |
| + } else { |
| + WARN(1, "This handle is already registered"); |
| + goto already_registered; |
| + } |
| + } |
| + |
| + if (handle->tipc.obj_id > new_handle->tipc.obj_id) |
| + new = &((*new)->rb_left); |
| + else |
| + new = &((*new)->rb_right); |
| + } |
| + |
| + rb_link_node(&new_handle->node, parent, new); |
| + rb_insert_color(&new_handle->node, &vds->shared_handles); |
| + |
| +already_registered: |
| + mutex_unlock(&vds->shared_handles_lock); |
| +} |
| + |
| +static struct tipc_shared_handle *tipc_shared_handle_take(struct tipc_virtio_dev |
| + *vds, |
| + trusty_shared_mem_id_t |
| + obj_id) |
| +{ |
| + struct rb_node *node; |
| + struct tipc_shared_handle *out = NULL; |
| + |
| + mutex_lock(&vds->shared_handles_lock); |
| + |
| + node = vds->shared_handles.rb_node; |
| + while (node) { |
| + struct tipc_shared_handle *handle = |
| + rb_entry(node, struct tipc_shared_handle, node); |
| + if (obj_id == handle->tipc.obj_id) { |
| + rb_erase(node, &vds->shared_handles); |
| + out = handle; |
| + break; |
| + } else if (obj_id < handle->tipc.obj_id) { |
| + node = node->rb_left; |
| + } else { |
| + node = node->rb_right; |
| + } |
| + } |
| + |
| + mutex_unlock(&vds->shared_handles_lock); |
| + |
| + return out; |
| +} |
| + |
| +static int tipc_shared_handle_drop(struct tipc_shared_handle *shared_handle) |
| +{ |
| + int ret; |
| + struct tipc_virtio_dev *vds = shared_handle->vds; |
| + struct device *dev = tipc_shared_handle_dev(shared_handle); |
| + |
| + if (shared_handle->shared) { |
| + /* |
| + * If this warning fires, it means this shared handle was still |
| + * in the set of active handles. This shouldn't happen (calling |
| + * code should ensure it is out if the tree) but this serves as |
| + * an extra check before it is released. |
| + * |
| + * However, the take itself should clean this incorrect state up |
| + * by removing the handle from the tree. |
| + * |
| + * This warning is only applicable when registering a handle |
| + * multiple times is not allowed, i.e. when dma_buf doesn't own |
| + * the handle. |
| + */ |
| + WARN_ON(tipc_shared_handle_take(vds, |
| + shared_handle->tipc.obj_id)); |
| + |
| + ret = trusty_reclaim_memory(dev, |
| + shared_handle->tipc.obj_id, |
| + shared_handle->sgt->sgl, |
| + shared_handle->sgt->orig_nents); |
| + if (ret) { |
| + /* |
| + * We can't safely release this, it may still be in |
| + * use outside Linux. |
| + */ |
| + dev_warn(dev, "Failed to drop handle, leaking...\n"); |
| + return ret; |
| + } |
| + } |
| + |
| + if (shared_handle->sgt) |
| + dma_buf_unmap_attachment(shared_handle->attach, |
| + shared_handle->sgt, DMA_BIDIRECTIONAL); |
| + if (shared_handle->attach) |
| + dma_buf_detach(shared_handle->dma_buf, shared_handle->attach); |
| + if (shared_handle->dma_buf) |
| + dma_buf_put(shared_handle->dma_buf); |
| + |
| + kfree(shared_handle); |
| + |
| + return 0; |
| +} |
| + |
| +/*****************************************************************************/ |
| + |
| +struct tipc_chan *tipc_create_channel(struct device *dev, |
| + const struct tipc_chan_ops *ops, |
| + void *ops_arg) |
| +{ |
| + struct virtio_device *vd; |
| + struct tipc_chan *chan; |
| + struct tipc_virtio_dev *vds; |
| + |
| + mutex_lock(&tipc_devices_lock); |
| + if (dev) { |
| + vd = container_of(dev, struct virtio_device, dev); |
| + } else { |
| + vd = default_vdev; |
| + if (!vd) { |
| + mutex_unlock(&tipc_devices_lock); |
| + return ERR_PTR(-ENOENT); |
| + } |
| + } |
| + vds = vd->priv; |
| + kref_get(&vds->refcount); |
| + mutex_unlock(&tipc_devices_lock); |
| + |
| + chan = vds_create_channel(vds, ops, ops_arg); |
| + kref_put(&vds->refcount, _free_vds); |
| + return chan; |
| +} |
| +EXPORT_SYMBOL(tipc_create_channel); |
| + |
| +struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan) |
| +{ |
| + return vds_alloc_msg_buf(chan->vds, true); |
| +} |
| +EXPORT_SYMBOL(tipc_chan_get_rxbuf); |
| + |
| +void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb) |
| +{ |
| + vds_free_msg_buf(chan->vds, mb); |
| +} |
| +EXPORT_SYMBOL(tipc_chan_put_rxbuf); |
| + |
| +struct tipc_msg_buf *tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, |
| + long timeout) |
| +{ |
| + return vds_get_txbuf(chan->vds, timeout); |
| +} |
| +EXPORT_SYMBOL(tipc_chan_get_txbuf_timeout); |
| + |
| +void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb) |
| +{ |
| + vds_put_txbuf(chan->vds, mb); |
| +} |
| +EXPORT_SYMBOL(tipc_chan_put_txbuf); |
| + |
| +int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb) |
| +{ |
| + int err; |
| + |
| + mutex_lock(&chan->lock); |
| + switch (chan->state) { |
| + case TIPC_CONNECTED: |
| + fill_msg_hdr(mb, chan->local, chan->remote); |
| + err = vds_queue_txbuf(chan->vds, mb); |
| + if (err) { |
| + /* this should never happen */ |
| + dev_err(&chan->vds->vdev->dev, |
| + "%s: failed to queue tx buffer (%d)\n", |
| + __func__, err); |
| + } |
| + break; |
| + case TIPC_DISCONNECTED: |
| + case TIPC_CONNECTING: |
| + err = -ENOTCONN; |
| + break; |
| + case TIPC_STALE: |
| + err = -ESHUTDOWN; |
| + break; |
| + default: |
| + err = -EBADFD; |
| + dev_err(&chan->vds->vdev->dev, |
| + "%s: unexpected channel state %d\n", |
| + __func__, chan->state); |
| + } |
| + mutex_unlock(&chan->lock); |
| + return err; |
| +} |
| +EXPORT_SYMBOL(tipc_chan_queue_msg); |
| + |
| + |
| +int tipc_chan_connect(struct tipc_chan *chan, const char *name) |
| +{ |
| + int err; |
| + struct tipc_ctrl_msg *msg; |
| + struct tipc_conn_req_body *body; |
| + struct tipc_msg_buf *txbuf; |
| + |
| + txbuf = vds_get_txbuf(chan->vds, TXBUF_TIMEOUT); |
| + if (IS_ERR(txbuf)) |
| + return PTR_ERR(txbuf); |
| + |
| + /* reserve space for connection request control message */ |
| + msg = mb_put_data(txbuf, sizeof(*msg) + sizeof(*body)); |
| + body = (struct tipc_conn_req_body *)msg->body; |
| + |
| + /* fill message */ |
| + msg->type = TIPC_CTRL_MSGTYPE_CONN_REQ; |
| + msg->body_len = sizeof(*body); |
| + |
| + strncpy(body->name, name, sizeof(body->name)); |
| + body->name[sizeof(body->name)-1] = '\0'; |
| + |
| + mutex_lock(&chan->lock); |
| + switch (chan->state) { |
| + case TIPC_DISCONNECTED: |
| + /* save service name we are connecting to */ |
| + strcpy(chan->srv_name, body->name); |
| + |
| + fill_msg_hdr(txbuf, chan->local, TIPC_CTRL_ADDR); |
| + err = vds_queue_txbuf(chan->vds, txbuf); |
| + if (err) { |
| + /* this should never happen */ |
| + dev_err(&chan->vds->vdev->dev, |
| + "%s: failed to queue tx buffer (%d)\n", |
| + __func__, err); |
| + } else { |
| + chan->state = TIPC_CONNECTING; |
| + txbuf = NULL; /* prevents discarding buffer */ |
| + } |
| + break; |
| + case TIPC_CONNECTED: |
| + case TIPC_CONNECTING: |
| + /* check if we are trying to connect to the same service */ |
| + if (strcmp(chan->srv_name, body->name) == 0) |
| + err = 0; |
| + else |
| + if (chan->state == TIPC_CONNECTING) |
| + err = -EALREADY; /* in progress */ |
| + else |
| + err = -EISCONN; /* already connected */ |
| + break; |
| + |
| + case TIPC_STALE: |
| + err = -ESHUTDOWN; |
| + break; |
| + default: |
| + err = -EBADFD; |
| + dev_err(&chan->vds->vdev->dev, |
| + "%s: unexpected channel state %d\n", |
| + __func__, chan->state); |
| + break; |
| + } |
| + mutex_unlock(&chan->lock); |
| + |
| + if (txbuf) |
| + tipc_chan_put_txbuf(chan, txbuf); /* discard it */ |
| + |
| + return err; |
| +} |
| +EXPORT_SYMBOL(tipc_chan_connect); |
| + |
| +int tipc_chan_shutdown(struct tipc_chan *chan) |
| +{ |
| + int err; |
| + struct tipc_ctrl_msg *msg; |
| + struct tipc_disc_req_body *body; |
| + struct tipc_msg_buf *txbuf = NULL; |
| + |
| + /* get tx buffer */ |
| + txbuf = vds_get_txbuf(chan->vds, TXBUF_TIMEOUT); |
| + if (IS_ERR(txbuf)) |
| + return PTR_ERR(txbuf); |
| + |
| + mutex_lock(&chan->lock); |
| + if (chan->state == TIPC_CONNECTED || chan->state == TIPC_CONNECTING) { |
| + /* reserve space for disconnect request control message */ |
| + msg = mb_put_data(txbuf, sizeof(*msg) + sizeof(*body)); |
| + body = (struct tipc_disc_req_body *)msg->body; |
| + |
| + msg->type = TIPC_CTRL_MSGTYPE_DISC_REQ; |
| + msg->body_len = sizeof(*body); |
| + body->target = chan->remote; |
| + |
| + fill_msg_hdr(txbuf, chan->local, TIPC_CTRL_ADDR); |
| + err = vds_queue_txbuf(chan->vds, txbuf); |
| + if (err) { |
| + /* this should never happen */ |
| + dev_err(&chan->vds->vdev->dev, |
| + "%s: failed to queue tx buffer (%d)\n", |
| + __func__, err); |
| + } |
| + } else { |
| + err = -ENOTCONN; |
| + } |
| + chan->state = TIPC_STALE; |
| + mutex_unlock(&chan->lock); |
| + |
| + if (err) { |
| + /* release buffer */ |
| + tipc_chan_put_txbuf(chan, txbuf); |
| + } |
| + |
| + return err; |
| +} |
| +EXPORT_SYMBOL(tipc_chan_shutdown); |
| + |
| +void tipc_chan_destroy(struct tipc_chan *chan) |
| +{ |
| + vds_del_channel(chan->vds, chan); |
| + kref_put(&chan->refcount, _free_chan); |
| +} |
| +EXPORT_SYMBOL(tipc_chan_destroy); |
| + |
| +/***************************************************************************/ |
| + |
| +struct tipc_dn_chan { |
| + int state; |
| + struct mutex lock; /* protects rx_msg_queue list and channel state */ |
| + struct tipc_chan *chan; |
| + wait_queue_head_t readq; |
| + struct completion reply_comp; |
| + struct list_head rx_msg_queue; |
| +}; |
| + |
| +static int dn_wait_for_reply(struct tipc_dn_chan *dn, int timeout) |
| +{ |
| + int ret; |
| + |
| + ret = wait_for_completion_interruptible_timeout(&dn->reply_comp, |
| + msecs_to_jiffies(timeout)); |
| + if (ret < 0) |
| + return ret; |
| + |
| + mutex_lock(&dn->lock); |
| + if (!ret) { |
| + /* no reply from remote */ |
| + dn->state = TIPC_STALE; |
| + ret = -ETIMEDOUT; |
| + } else { |
| + /* got reply */ |
| + if (dn->state == TIPC_CONNECTED) |
| + ret = 0; |
| + else if (dn->state == TIPC_DISCONNECTED) |
| + if (!list_empty(&dn->rx_msg_queue)) |
| + ret = 0; |
| + else |
| + ret = -ENOTCONN; |
| + else |
| + ret = -EIO; |
| + } |
| + mutex_unlock(&dn->lock); |
| + |
| + return ret; |
| +} |
| + |
| +static struct tipc_msg_buf *dn_handle_msg(void *data, |
| + struct tipc_msg_buf *rxbuf) |
| +{ |
| + struct tipc_dn_chan *dn = data; |
| + struct tipc_msg_buf *newbuf = rxbuf; |
| + |
| + mutex_lock(&dn->lock); |
| + if (dn->state == TIPC_CONNECTED) { |
| + /* get new buffer */ |
| + newbuf = tipc_chan_get_rxbuf(dn->chan); |
| + if (newbuf) { |
| + /* queue an old buffer and return a new one */ |
| + list_add_tail(&rxbuf->node, &dn->rx_msg_queue); |
| + wake_up_interruptible(&dn->readq); |
| + } else { |
| + /* |
| + * return an old buffer effectively discarding |
| + * incoming message |
| + */ |
| + dev_err(&dn->chan->vds->vdev->dev, |
| + "%s: discard incoming message\n", __func__); |
| + newbuf = rxbuf; |
| + } |
| + } |
| + mutex_unlock(&dn->lock); |
| + |
| + return newbuf; |
| +} |
| + |
| +static void dn_connected(struct tipc_dn_chan *dn) |
| +{ |
| + mutex_lock(&dn->lock); |
| + dn->state = TIPC_CONNECTED; |
| + |
| + /* complete all pending */ |
| + complete(&dn->reply_comp); |
| + |
| + mutex_unlock(&dn->lock); |
| +} |
| + |
| +static void dn_disconnected(struct tipc_dn_chan *dn) |
| +{ |
| + mutex_lock(&dn->lock); |
| + dn->state = TIPC_DISCONNECTED; |
| + |
| + /* complete all pending */ |
| + complete(&dn->reply_comp); |
| + |
| + /* wakeup all readers */ |
| + wake_up_interruptible_all(&dn->readq); |
| + |
| + mutex_unlock(&dn->lock); |
| +} |
| + |
| +static void dn_shutdown(struct tipc_dn_chan *dn) |
| +{ |
| + mutex_lock(&dn->lock); |
| + |
| + /* set state to STALE */ |
| + dn->state = TIPC_STALE; |
| + |
| + /* complete all pending */ |
| + complete(&dn->reply_comp); |
| + |
| + /* wakeup all readers */ |
| + wake_up_interruptible_all(&dn->readq); |
| + |
| + mutex_unlock(&dn->lock); |
| +} |
| + |
| +static void dn_handle_event(void *data, int event) |
| +{ |
| + struct tipc_dn_chan *dn = data; |
| + |
| + switch (event) { |
| + case TIPC_CHANNEL_SHUTDOWN: |
| + dn_shutdown(dn); |
| + break; |
| + |
| + case TIPC_CHANNEL_DISCONNECTED: |
| + dn_disconnected(dn); |
| + break; |
| + |
| + case TIPC_CHANNEL_CONNECTED: |
| + dn_connected(dn); |
| + break; |
| + |
| + default: |
| + dev_err(&dn->chan->vds->vdev->dev, |
| + "%s: unhandled event %d\n", __func__, event); |
| + break; |
| + } |
| +} |
| + |
| +static void dn_handle_release(void *data) |
| +{ |
| + kfree(data); |
| +} |
| + |
| +static const struct tipc_chan_ops _dn_ops = { |
| + .handle_msg = dn_handle_msg, |
| + .handle_event = dn_handle_event, |
| + .handle_release = dn_handle_release, |
| +}; |
| + |
| +#define cdev_to_cdn(c) container_of((c), struct tipc_cdev_node, cdev) |
| +#define cdn_to_vds(cdn) container_of((cdn), struct tipc_virtio_dev, cdev_node) |
| + |
| +static struct tipc_virtio_dev *_dn_lookup_vds(struct tipc_cdev_node *cdn) |
| +{ |
| + int ret; |
| + struct tipc_virtio_dev *vds = NULL; |
| + |
| + mutex_lock(&tipc_devices_lock); |
| + ret = idr_for_each(&tipc_devices, _match_data, cdn); |
| + if (ret) { |
| + vds = cdn_to_vds(cdn); |
| + kref_get(&vds->refcount); |
| + } |
| + mutex_unlock(&tipc_devices_lock); |
| + return vds; |
| +} |
| + |
| +static int tipc_open(struct inode *inode, struct file *filp) |
| +{ |
| + int ret; |
| + struct tipc_virtio_dev *vds; |
| + struct tipc_dn_chan *dn; |
| + struct tipc_cdev_node *cdn = cdev_to_cdn(inode->i_cdev); |
| + |
| + vds = _dn_lookup_vds(cdn); |
| + if (!vds) { |
| + ret = -ENOENT; |
| + goto err_vds_lookup; |
| + } |
| + |
| + dn = kzalloc(sizeof(*dn), GFP_KERNEL); |
| + if (!dn) { |
| + ret = -ENOMEM; |
| + goto err_alloc_chan; |
| + } |
| + |
| + mutex_init(&dn->lock); |
| + init_waitqueue_head(&dn->readq); |
| + init_completion(&dn->reply_comp); |
| + INIT_LIST_HEAD(&dn->rx_msg_queue); |
| + |
| + dn->state = TIPC_DISCONNECTED; |
| + |
| + dn->chan = vds_create_channel(vds, &_dn_ops, dn); |
| + if (IS_ERR(dn->chan)) { |
| + ret = PTR_ERR(dn->chan); |
| + goto err_create_chan; |
| + } |
| + |
| + filp->private_data = dn; |
| + kref_put(&vds->refcount, _free_vds); |
| + return 0; |
| + |
| +err_create_chan: |
| + kfree(dn); |
| +err_alloc_chan: |
| + kref_put(&vds->refcount, _free_vds); |
| +err_vds_lookup: |
| + return ret; |
| +} |
| + |
| + |
| +static int dn_connect_ioctl(struct tipc_dn_chan *dn, char __user *usr_name) |
| +{ |
| + int ret; |
| + char name[MAX_SRV_NAME_LEN]; |
| + |
| + /* copy in service name from user space */ |
| + ret = strncpy_from_user(name, usr_name, sizeof(name)); |
| + if (ret < 0) |
| + return ret; |
| + if (ret == sizeof(name)) |
| + return -ENAMETOOLONG; |
| + |
| + /* send connect request */ |
| + ret = tipc_chan_connect(dn->chan, name); |
| + if (ret) |
| + return ret; |
| + |
| + /* and wait for reply */ |
| + return dn_wait_for_reply(dn, REPLY_TIMEOUT); |
| +} |
| + |
| +static int dn_share_fd(struct tipc_dn_chan *dn, int fd, |
| + enum transfer_kind transfer_kind, |
| + struct tipc_shared_handle **out) |
| +{ |
| + int ret = 0; |
| + struct tipc_shared_handle *shared_handle = NULL; |
| + struct file *file = NULL; |
| + struct device *dev = &dn->chan->vds->vdev->dev; |
| + bool writable = false; |
| + pgprot_t prot; |
| + u64 tag = 0; |
| + trusty_shared_mem_id_t mem_id; |
| + bool lend; |
| + |
| + if (dn->state != TIPC_CONNECTED) { |
| + dev_dbg(dev, "Tried to share fd while not connected\n"); |
| + return -ENOTCONN; |
| + } |
| + |
| + file = fget(fd); |
| + if (!file) { |
| + dev_dbg(dev, "Invalid fd (%d)\n", fd); |
| + return -EBADF; |
| + } |
| + |
| + if (!(file->f_mode & FMODE_READ)) { |
| + dev_dbg(dev, "Cannot create write-only mapping\n"); |
| + fput(file); |
| + return -EACCES; |
| + } |
| + |
| + writable = file->f_mode & FMODE_WRITE; |
| + prot = writable ? PAGE_KERNEL : PAGE_KERNEL_RO; |
| + fput(file); |
| + file = NULL; |
| + |
| + ret = tipc_shared_handle_new(&shared_handle, dn->chan->vds); |
| + if (ret) |
| + return ret; |
| + |
| + shared_handle->dma_buf = dma_buf_get(fd); |
| + if (IS_ERR(shared_handle->dma_buf)) { |
| + ret = PTR_ERR(shared_handle->dma_buf); |
| + shared_handle->dma_buf = NULL; |
| + dev_dbg(dev, "Unable to get dma buf from fd (%d)\n", ret); |
| + goto cleanup_handle; |
| + } |
| + |
| + tag = trusty_dma_buf_get_ffa_tag(shared_handle->dma_buf); |
| + ret = trusty_dma_buf_get_shared_mem_id(shared_handle->dma_buf, &mem_id); |
| + /* |
| + * Buffers with a preallocated mem_id should only be sent to Trusty |
| + * using TRUSTY_SEND_SECURE. And conversely, TRUSTY_SEND_SECURE should |
| + * only be used to send buffers with preallcoated mem_id. |
| + */ |
| + if (!ret) { |
| + /* Use shared memory ID owned by dma_buf */ |
| + /* TODO: Enforce transfer_kind == TRUSTY_SEND_SECURE */ |
| + WARN_ONCE(transfer_kind != TRUSTY_SEND_SECURE, |
| + "Use TRUSTY_SEND_SECURE instead"); |
| + goto mem_id_allocated; |
| + } |
| + |
| + if (ret != -ENODATA) { |
| + dev_err(dev, "dma_buf can't be transferred (%d)\n", ret); |
| + goto cleanup_handle; |
| + } |
| + |
| + if (transfer_kind == TRUSTY_SEND_SECURE) { |
| + dev_err(dev, "No mem ID for TRUSTY_SEND_SECURE\n"); |
| + goto cleanup_handle; |
| + } |
| + lend = (transfer_kind == TRUSTY_LEND); |
| + |
| + shared_handle->attach = dma_buf_attach(shared_handle->dma_buf, dev); |
| + if (IS_ERR(shared_handle->attach)) { |
| + ret = PTR_ERR(shared_handle->attach); |
| + shared_handle->attach = NULL; |
| + dev_dbg(dev, "Unable to attach to dma_buf (%d)\n", ret); |
| + goto cleanup_handle; |
| + } |
| + |
| + shared_handle->sgt = dma_buf_map_attachment(shared_handle->attach, |
| + DMA_BIDIRECTIONAL); |
| + if (IS_ERR(shared_handle->sgt)) { |
| + ret = PTR_ERR(shared_handle->sgt); |
| + shared_handle->sgt = NULL; |
| + dev_dbg(dev, "Failed to match attachment (%d)\n", ret); |
| + goto cleanup_handle; |
| + } |
| + |
| + ret = trusty_transfer_memory(tipc_shared_handle_dev(shared_handle), |
| + &mem_id, shared_handle->sgt->sgl, |
| + shared_handle->sgt->orig_nents, prot, tag, |
| + lend); |
| + |
| + if (ret < 0) { |
| + dev_dbg(dev, "Transferring memory failed: %d\n", ret); |
| + /* |
| + * The handle now has a sgt containing the pages, so we no |
| + * longer need to clean up the pages directly. |
| + */ |
| + goto cleanup_handle; |
| + } |
| + shared_handle->shared = true; |
| + |
| +mem_id_allocated: |
| + shared_handle->tipc.obj_id = mem_id; |
| + shared_handle->tipc.size = shared_handle->dma_buf->size; |
| + shared_handle->tipc.tag = tag; |
| + *out = shared_handle; |
| + return 0; |
| + |
| +cleanup_handle: |
| + tipc_shared_handle_drop(shared_handle); |
| + return ret; |
| +} |
| + |
| +static ssize_t txbuf_write_iter(struct tipc_msg_buf *txbuf, |
| + struct iov_iter *iter) |
| +{ |
| + size_t len; |
| + /* message length */ |
| + len = iov_iter_count(iter); |
| + |
| + /* check available space */ |
| + if (len > mb_avail_space(txbuf)) |
| + return -EMSGSIZE; |
| + |
| + /* copy in message data */ |
| + if (copy_from_iter(mb_put_data(txbuf, len), len, iter) != len) |
| + return -EFAULT; |
| + |
| + return len; |
| +} |
| + |
| +static ssize_t txbuf_write_handles(struct tipc_msg_buf *txbuf, |
| + struct tipc_shared_handle **shm_handles, |
| + size_t shm_cnt) |
| +{ |
| + size_t idx; |
| + |
| + /* message length */ |
| + size_t len = shm_cnt * sizeof(struct tipc_shm); |
| + |
| + /* check available space */ |
| + if (len > mb_avail_space(txbuf)) |
| + return -EMSGSIZE; |
| + |
| + /* copy over handles */ |
| + for (idx = 0; idx < shm_cnt; idx++) { |
| + memcpy(mb_put_data(txbuf, sizeof(struct tipc_shm)), |
| + &shm_handles[idx]->tipc, |
| + sizeof(struct tipc_shm)); |
| + } |
| + |
| + txbuf->shm_cnt += shm_cnt; |
| + |
| + return len; |
| +} |
| + |
| +static long filp_send_ioctl(struct file *filp, |
| + const struct tipc_send_msg_req __user *arg) |
| +{ |
| + struct tipc_send_msg_req req; |
| + struct iovec fast_iovs[UIO_FASTIOV]; |
| + struct iovec *iov = fast_iovs; |
| + struct iov_iter iter; |
| + struct trusty_shm *shm = NULL; |
| + struct tipc_shared_handle **shm_handles = NULL; |
| + int shm_idx = 0; |
| + int release_idx; |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + struct tipc_virtio_dev *vds = dn->chan->vds; |
| + struct device *dev = &vds->vdev->dev; |
| + long timeout = TXBUF_TIMEOUT; |
| + struct tipc_msg_buf *txbuf = NULL; |
| + long ret = 0; |
| + ssize_t data_len = 0; |
| + ssize_t shm_len = 0; |
| + |
| + if (copy_from_user(&req, arg, sizeof(req))) |
| + return -EFAULT; |
| + |
| + if (req.shm_cnt > U16_MAX) |
| + return -E2BIG; |
| + |
| + shm = kmalloc_array(req.shm_cnt, sizeof(*shm), GFP_KERNEL); |
| + if (!shm) |
| + return -ENOMEM; |
| + |
| + shm_handles = kmalloc_array(req.shm_cnt, sizeof(*shm_handles), |
| + GFP_KERNEL); |
| + if (!shm_handles) { |
| + ret = -ENOMEM; |
| + goto shm_handles_alloc_failed; |
| + } |
| + |
| + if (copy_from_user(shm, u64_to_user_ptr(req.shm), |
| + req.shm_cnt * sizeof(struct trusty_shm))) { |
| + ret = -EFAULT; |
| + goto load_shm_args_failed; |
| + } |
| + |
| + ret = import_iovec(READ, u64_to_user_ptr(req.iov), req.iov_cnt, |
| + ARRAY_SIZE(fast_iovs), &iov, &iter); |
| + if (ret < 0) { |
| + dev_dbg(dev, "Failed to import iovec\n"); |
| + goto iov_import_failed; |
| + } |
| + |
| + for (shm_idx = 0; shm_idx < req.shm_cnt; shm_idx++) { |
| + switch (shm[shm_idx].transfer) { |
| + case TRUSTY_SHARE: |
| + case TRUSTY_LEND: |
| + case TRUSTY_SEND_SECURE: |
| + break; |
| + default: |
| + dev_err(dev, "Unknown transfer type: 0x%x\n", |
| + shm[shm_idx].transfer); |
| + goto shm_share_failed; |
| + } |
| + ret = dn_share_fd(dn, shm[shm_idx].fd, shm[shm_idx].transfer, |
| + &shm_handles[shm_idx]); |
| + if (ret) { |
| + dev_dbg(dev, "Forwarding memory failed\n" |
| + ); |
| + goto shm_share_failed; |
| + } |
| + } |
| + |
| + if (filp->f_flags & O_NONBLOCK) |
| + timeout = 0; |
| + |
| + txbuf = tipc_chan_get_txbuf_timeout(dn->chan, timeout); |
| + if (IS_ERR(txbuf)) { |
| + dev_dbg(dev, "Failed to get txbuffer\n"); |
| + ret = PTR_ERR(txbuf); |
| + goto get_txbuf_failed; |
| + } |
| + |
| + data_len = txbuf_write_iter(txbuf, &iter); |
| + if (data_len < 0) { |
| + ret = data_len; |
| + goto txbuf_write_failed; |
| + } |
| + |
| + shm_len = txbuf_write_handles(txbuf, shm_handles, req.shm_cnt); |
| + if (shm_len < 0) { |
| + ret = shm_len; |
| + goto txbuf_write_failed; |
| + } |
| + |
| + /* |
| + * These need to be aded to the index before queueing the message. |
| + * As soon as the message is sent, we may receive a message back from |
| + * Trusty saying it's no longer in use, and the shared_handle needs |
| + * to be there when that happens. |
| + */ |
| + for (shm_idx = 0; shm_idx < req.shm_cnt; shm_idx++) |
| + tipc_shared_handle_register(shm_handles[shm_idx]); |
| + |
| + ret = tipc_chan_queue_msg(dn->chan, txbuf); |
| + |
| + if (ret) |
| + goto queue_failed; |
| + |
| + ret = data_len; |
| + |
| +common_cleanup: |
| + kfree(iov); |
| +iov_import_failed: |
| +load_shm_args_failed: |
| + kfree(shm_handles); |
| +shm_handles_alloc_failed: |
| + kfree(shm); |
| + return ret; |
| + |
| +queue_failed: |
| + for (release_idx = 0; release_idx < req.shm_cnt; release_idx++) |
| + tipc_shared_handle_take(vds, |
| + shm_handles[release_idx]->tipc.obj_id); |
| +txbuf_write_failed: |
| + tipc_chan_put_txbuf(dn->chan, txbuf); |
| +get_txbuf_failed: |
| +shm_share_failed: |
| + for (shm_idx--; shm_idx >= 0; shm_idx--) |
| + tipc_shared_handle_drop(shm_handles[shm_idx]); |
| + goto common_cleanup; |
| +} |
| + |
| +static long tipc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| +{ |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + |
| + switch (cmd) { |
| + case TIPC_IOC_CONNECT: |
| + return dn_connect_ioctl(dn, (char __user *)arg); |
| + case TIPC_IOC_SEND_MSG: |
| + return filp_send_ioctl(filp, |
| + (const struct tipc_send_msg_req __user *) |
| + arg); |
| + default: |
| + dev_dbg(&dn->chan->vds->vdev->dev, |
| + "Unhandled ioctl cmd: 0x%x\n", cmd); |
| + return -ENOTTY; |
| + } |
| +} |
| + |
| +#ifdef CONFIG_COMPAT |
| +static long tipc_compat_ioctl(struct file *filp, |
| + unsigned int cmd, unsigned long arg) |
| +{ |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + |
| + switch (cmd) { |
| + case TIPC_IOC32_CONNECT: |
| + cmd = TIPC_IOC_CONNECT; |
| + break; |
| + default: |
| + dev_dbg(&dn->chan->vds->vdev->dev, |
| + "Unhandled compat ioctl command: 0x%x\n", cmd); |
| + return -ENOTTY; |
| + } |
| + return tipc_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); |
| +} |
| +#endif |
| + |
| +static inline bool _got_rx(struct tipc_dn_chan *dn) |
| +{ |
| + if (dn->state != TIPC_CONNECTED) |
| + return true; |
| + |
| + if (!list_empty(&dn->rx_msg_queue)) |
| + return true; |
| + |
| + return false; |
| +} |
| + |
| +static ssize_t tipc_read_iter(struct kiocb *iocb, struct iov_iter *iter) |
| +{ |
| + ssize_t ret; |
| + size_t len; |
| + struct tipc_msg_buf *mb; |
| + struct file *filp = iocb->ki_filp; |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + |
| + mutex_lock(&dn->lock); |
| + |
| + while (list_empty(&dn->rx_msg_queue)) { |
| + if (dn->state != TIPC_CONNECTED) { |
| + if (dn->state == TIPC_CONNECTING) |
| + ret = -ENOTCONN; |
| + else if (dn->state == TIPC_DISCONNECTED) |
| + ret = -ENOTCONN; |
| + else if (dn->state == TIPC_STALE) |
| + ret = -ESHUTDOWN; |
| + else |
| + ret = -EBADFD; |
| + goto out; |
| + } |
| + |
| + mutex_unlock(&dn->lock); |
| + |
| + if (filp->f_flags & O_NONBLOCK) |
| + return -EAGAIN; |
| + |
| + if (wait_event_interruptible(dn->readq, _got_rx(dn))) |
| + return -ERESTARTSYS; |
| + |
| + mutex_lock(&dn->lock); |
| + } |
| + |
| + mb = list_first_entry(&dn->rx_msg_queue, struct tipc_msg_buf, node); |
| + |
| + len = mb_avail_data(mb); |
| + if (len > iov_iter_count(iter)) { |
| + ret = -EMSGSIZE; |
| + goto out; |
| + } |
| + |
| + if (copy_to_iter(mb_get_data(mb, len), len, iter) != len) { |
| + ret = -EFAULT; |
| + goto out; |
| + } |
| + |
| + ret = len; |
| + list_del(&mb->node); |
| + tipc_chan_put_rxbuf(dn->chan, mb); |
| + |
| +out: |
| + mutex_unlock(&dn->lock); |
| + return ret; |
| +} |
| + |
| +static ssize_t tipc_write_iter(struct kiocb *iocb, struct iov_iter *iter) |
| +{ |
| + struct file *filp = iocb->ki_filp; |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + long timeout = TXBUF_TIMEOUT; |
| + struct tipc_msg_buf *txbuf = NULL; |
| + ssize_t ret = 0; |
| + ssize_t len = 0; |
| + |
| + if (filp->f_flags & O_NONBLOCK) |
| + timeout = 0; |
| + |
| + txbuf = tipc_chan_get_txbuf_timeout(dn->chan, timeout); |
| + |
| + if (IS_ERR(txbuf)) |
| + return PTR_ERR(txbuf); |
| + |
| + len = txbuf_write_iter(txbuf, iter); |
| + if (len < 0) |
| + goto err_out; |
| + |
| + /* queue message */ |
| + ret = tipc_chan_queue_msg(dn->chan, txbuf); |
| + if (ret) |
| + goto err_out; |
| + |
| + return len; |
| + |
| +err_out: |
| + tipc_chan_put_txbuf(dn->chan, txbuf); |
| + return ret; |
| +} |
| + |
| +static __poll_t tipc_poll(struct file *filp, poll_table *wait) |
| +{ |
| + __poll_t mask = 0; |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + |
| + mutex_lock(&dn->lock); |
| + |
| + poll_wait(filp, &dn->readq, wait); |
| + |
| + /* Writes always succeed for now */ |
| + mask |= EPOLLOUT | EPOLLWRNORM; |
| + |
| + if (!list_empty(&dn->rx_msg_queue)) |
| + mask |= EPOLLIN | EPOLLRDNORM; |
| + |
| + if (dn->state != TIPC_CONNECTED) |
| + mask |= EPOLLERR; |
| + |
| + mutex_unlock(&dn->lock); |
| + return mask; |
| +} |
| + |
| + |
| +static int tipc_release(struct inode *inode, struct file *filp) |
| +{ |
| + struct tipc_dn_chan *dn = filp->private_data; |
| + |
| + dn_shutdown(dn); |
| + |
| + /* free all pending buffers */ |
| + vds_free_msg_buf_list(dn->chan->vds, &dn->rx_msg_queue); |
| + |
| + /* shutdown channel */ |
| + tipc_chan_shutdown(dn->chan); |
| + |
| + /* and destroy it */ |
| + tipc_chan_destroy(dn->chan); |
| + |
| + return 0; |
| +} |
| + |
| +static const struct file_operations tipc_fops = { |
| + .open = tipc_open, |
| + .release = tipc_release, |
| + .unlocked_ioctl = tipc_ioctl, |
| +#ifdef CONFIG_COMPAT |
| + .compat_ioctl = tipc_compat_ioctl, |
| +#endif |
| + .read_iter = tipc_read_iter, |
| + .write_iter = tipc_write_iter, |
| + .poll = tipc_poll, |
| + .owner = THIS_MODULE, |
| +}; |
| + |
| +/*****************************************************************************/ |
| + |
| +static void chan_trigger_event(struct tipc_chan *chan, int event) |
| +{ |
| + if (!event) |
| + return; |
| + |
| + chan->ops->handle_event(chan->ops_arg, event); |
| +} |
| + |
| +static void _cleanup_vq(struct tipc_virtio_dev *vds, struct virtqueue *vq) |
| +{ |
| + struct tipc_msg_buf *mb; |
| + |
| + while ((mb = virtqueue_detach_unused_buf(vq)) != NULL) |
| + vds_free_msg_buf(vds, mb); |
| +} |
| + |
| +static int _create_cdev_node(struct device *parent, |
| + struct tipc_cdev_node *cdn, |
| + const char *name) |
| +{ |
| + int ret; |
| + dev_t devt; |
| + |
| + if (!name) { |
| + dev_dbg(parent, "%s: cdev name has to be provided\n", |
| + __func__); |
| + return -EINVAL; |
| + } |
| + |
| + /* allocate minor */ |
| + ret = idr_alloc(&tipc_devices, cdn, 0, MAX_DEVICES, GFP_KERNEL); |
| + if (ret < 0) { |
| + dev_dbg(parent, "%s: failed (%d) to get id\n", |
| + __func__, ret); |
| + return ret; |
| + } |
| + |
| + cdn->minor = ret; |
| + cdev_init(&cdn->cdev, &tipc_fops); |
| + cdn->cdev.owner = THIS_MODULE; |
| + |
| + /* Add character device */ |
| + devt = MKDEV(tipc_major, cdn->minor); |
| + ret = cdev_add(&cdn->cdev, devt, 1); |
| + if (ret) { |
| + dev_dbg(parent, "%s: cdev_add failed (%d)\n", |
| + __func__, ret); |
| + goto err_add_cdev; |
| + } |
| + |
| + /* Create a device node */ |
| + cdn->dev = device_create(tipc_class, parent, |
| + devt, NULL, "trusty-ipc-%s", name); |
| + if (IS_ERR(cdn->dev)) { |
| + ret = PTR_ERR(cdn->dev); |
| + dev_dbg(parent, "%s: device_create failed: %d\n", |
| + __func__, ret); |
| + goto err_device_create; |
| + } |
| + |
| + return 0; |
| + |
| +err_device_create: |
| + cdn->dev = NULL; |
| + cdev_del(&cdn->cdev); |
| +err_add_cdev: |
| + idr_remove(&tipc_devices, cdn->minor); |
| + return ret; |
| +} |
| + |
| +static void create_cdev_node(struct tipc_virtio_dev *vds, |
| + struct tipc_cdev_node *cdn) |
| +{ |
| + int err; |
| + |
| + mutex_lock(&tipc_devices_lock); |
| + |
| + if (!default_vdev) { |
| + kref_get(&vds->refcount); |
| + default_vdev = vds->vdev; |
| + } |
| + |
| + if (vds->cdev_name[0] && !cdn->dev) { |
| + kref_get(&vds->refcount); |
| + err = _create_cdev_node(&vds->vdev->dev, cdn, vds->cdev_name); |
| + if (err) { |
| + dev_err(&vds->vdev->dev, |
| + "failed (%d) to create cdev node\n", err); |
| + kref_put(&vds->refcount, _free_vds); |
| + } |
| + } |
| + mutex_unlock(&tipc_devices_lock); |
| +} |
| + |
| +static void destroy_cdev_node(struct tipc_virtio_dev *vds, |
| + struct tipc_cdev_node *cdn) |
| +{ |
| + mutex_lock(&tipc_devices_lock); |
| + if (cdn->dev) { |
| + device_destroy(tipc_class, MKDEV(tipc_major, cdn->minor)); |
| + cdev_del(&cdn->cdev); |
| + idr_remove(&tipc_devices, cdn->minor); |
| + cdn->dev = NULL; |
| + kref_put(&vds->refcount, _free_vds); |
| + } |
| + |
| + if (default_vdev == vds->vdev) { |
| + default_vdev = NULL; |
| + kref_put(&vds->refcount, _free_vds); |
| + } |
| + |
| + mutex_unlock(&tipc_devices_lock); |
| +} |
| + |
| +static void _go_online(struct tipc_virtio_dev *vds) |
| +{ |
| + mutex_lock(&vds->lock); |
| + if (vds->state == VDS_OFFLINE) |
| + vds->state = VDS_ONLINE; |
| + mutex_unlock(&vds->lock); |
| + |
| + create_cdev_node(vds, &vds->cdev_node); |
| + |
| + dev_info(&vds->vdev->dev, "is online\n"); |
| +} |
| + |
| +static void _go_offline(struct tipc_virtio_dev *vds) |
| +{ |
| + struct tipc_chan *chan; |
| + |
| + /* change state to OFFLINE */ |
| + mutex_lock(&vds->lock); |
| + if (vds->state != VDS_ONLINE) { |
| + mutex_unlock(&vds->lock); |
| + return; |
| + } |
| + vds->state = VDS_OFFLINE; |
| + mutex_unlock(&vds->lock); |
| + |
| + /* wakeup all waiters */ |
| + wake_up_interruptible_all(&vds->sendq); |
| + |
| + /* shutdown all channels */ |
| + while ((chan = vds_lookup_channel(vds, TIPC_ANY_ADDR))) { |
| + mutex_lock(&chan->lock); |
| + chan->state = TIPC_STALE; |
| + chan->remote = 0; |
| + chan_trigger_event(chan, TIPC_CHANNEL_SHUTDOWN); |
| + mutex_unlock(&chan->lock); |
| + kref_put(&chan->refcount, _free_chan); |
| + } |
| + |
| + /* shutdown device node */ |
| + destroy_cdev_node(vds, &vds->cdev_node); |
| + |
| + dev_info(&vds->vdev->dev, "is offline\n"); |
| +} |
| + |
| +static void _handle_conn_rsp(struct tipc_virtio_dev *vds, |
| + struct tipc_conn_rsp_body *rsp, size_t len) |
| +{ |
| + struct tipc_chan *chan; |
| + |
| + if (sizeof(*rsp) != len) { |
| + dev_err(&vds->vdev->dev, "%s: Invalid response length %zd\n", |
| + __func__, len); |
| + return; |
| + } |
| + |
| + dev_dbg(&vds->vdev->dev, |
| + "%s: connection response: for addr 0x%x: status %d remote addr 0x%x\n", |
| + __func__, rsp->target, rsp->status, rsp->remote); |
| + |
| + /* Lookup channel */ |
| + chan = vds_lookup_channel(vds, rsp->target); |
| + if (chan) { |
| + mutex_lock(&chan->lock); |
| + if (chan->state == TIPC_CONNECTING) { |
| + if (!rsp->status) { |
| + chan->state = TIPC_CONNECTED; |
| + chan->remote = rsp->remote; |
| + chan->max_msg_cnt = rsp->max_msg_cnt; |
| + chan->max_msg_size = rsp->max_msg_size; |
| + chan_trigger_event(chan, |
| + TIPC_CHANNEL_CONNECTED); |
| + } else { |
| + chan->state = TIPC_DISCONNECTED; |
| + chan->remote = 0; |
| + chan_trigger_event(chan, |
| + TIPC_CHANNEL_DISCONNECTED); |
| + } |
| + } |
| + mutex_unlock(&chan->lock); |
| + kref_put(&chan->refcount, _free_chan); |
| + } |
| +} |
| + |
| +static void _handle_disc_req(struct tipc_virtio_dev *vds, |
| + struct tipc_disc_req_body *req, size_t len) |
| +{ |
| + struct tipc_chan *chan; |
| + |
| + if (sizeof(*req) != len) { |
| + dev_err(&vds->vdev->dev, "%s: Invalid request length %zd\n", |
| + __func__, len); |
| + return; |
| + } |
| + |
| + dev_dbg(&vds->vdev->dev, "%s: disconnect request: for addr 0x%x\n", |
| + __func__, req->target); |
| + |
| + chan = vds_lookup_channel(vds, req->target); |
| + if (chan) { |
| + mutex_lock(&chan->lock); |
| + if (chan->state == TIPC_CONNECTED || |
| + chan->state == TIPC_CONNECTING) { |
| + chan->state = TIPC_DISCONNECTED; |
| + chan->remote = 0; |
| + chan_trigger_event(chan, TIPC_CHANNEL_DISCONNECTED); |
| + } |
| + mutex_unlock(&chan->lock); |
| + kref_put(&chan->refcount, _free_chan); |
| + } |
| +} |
| + |
| +static void _handle_release(struct tipc_virtio_dev *vds, |
| + struct tipc_release_body *req, size_t len) |
| +{ |
| + struct tipc_shared_handle *handle = NULL; |
| + struct device *dev = &vds->vdev->dev; |
| + int ret = 0; |
| + |
| + if (len < sizeof(*req)) { |
| + dev_err(dev, "Received undersized release control message\n"); |
| + return; |
| + } |
| + |
| + handle = tipc_shared_handle_take(vds, req->id); |
| + if (!handle) { |
| + dev_err(dev, |
| + "Received release control message for untracked handle: 0x%llx\n", |
| + req->id); |
| + return; |
| + } |
| + |
| + ret = tipc_shared_handle_drop(handle); |
| + |
| + if (ret) { |
| + dev_err(dev, |
| + "Failed to release handle 0x%llx upon request: (%d)\n", |
| + req->id, ret); |
| + /* |
| + * Put the handle back in case we got a spurious release now and |
| + * get a real one later. This path should not happen, we're |
| + * just trying to be robust. |
| + */ |
| + tipc_shared_handle_register(handle); |
| + } |
| +} |
| + |
| +static void _handle_ctrl_msg(struct tipc_virtio_dev *vds, |
| + void *data, int len, u32 src) |
| +{ |
| + struct tipc_ctrl_msg *msg = data; |
| + |
| + if ((len < sizeof(*msg)) || (sizeof(*msg) + msg->body_len != len)) { |
| + dev_err(&vds->vdev->dev, |
| + "%s: Invalid message length ( %d vs. %d)\n", |
| + __func__, (int)(sizeof(*msg) + msg->body_len), len); |
| + return; |
| + } |
| + |
| + dev_dbg(&vds->vdev->dev, |
| + "%s: Incoming ctrl message: src 0x%x type %d len %d\n", |
| + __func__, src, msg->type, msg->body_len); |
| + |
| + switch (msg->type) { |
| + case TIPC_CTRL_MSGTYPE_GO_ONLINE: |
| + _go_online(vds); |
| + break; |
| + |
| + case TIPC_CTRL_MSGTYPE_GO_OFFLINE: |
| + _go_offline(vds); |
| + break; |
| + |
| + case TIPC_CTRL_MSGTYPE_CONN_RSP: |
| + _handle_conn_rsp(vds, (struct tipc_conn_rsp_body *)msg->body, |
| + msg->body_len); |
| + break; |
| + |
| + case TIPC_CTRL_MSGTYPE_DISC_REQ: |
| + _handle_disc_req(vds, (struct tipc_disc_req_body *)msg->body, |
| + msg->body_len); |
| + break; |
| + |
| + case TIPC_CTRL_MSGTYPE_RELEASE: |
| + _handle_release(vds, (struct tipc_release_body *)msg->body, |
| + msg->body_len); |
| + break; |
| + |
| + default: |
| + dev_warn(&vds->vdev->dev, |
| + "%s: Unexpected message type: %d\n", |
| + __func__, msg->type); |
| + } |
| +} |
| + |
| +static void handle_dropped_chan_msg(struct tipc_virtio_dev *vds, |
| + struct tipc_msg_buf *mb, |
| + struct tipc_msg_hdr *msg) |
| +{ |
| + int shm_idx; |
| + struct tipc_shm *shm; |
| + struct tipc_shared_handle *shared_handle; |
| + struct device *dev = &vds->vdev->dev; |
| + size_t len; |
| + |
| + if (msg->len < msg->shm_cnt * sizeof(*shm)) { |
| + dev_err(dev, "shm_cnt does not fit in dropped message"); |
| + /* The message is corrupt, so we can't recover resources */ |
| + return; |
| + } |
| + |
| + len = msg->len - msg->shm_cnt * sizeof(*shm); |
| + /* skip normal data */ |
| + (void)mb_get_data(mb, len); |
| + |
| + for (shm_idx = 0; shm_idx < msg->shm_cnt; shm_idx++) { |
| + shm = mb_get_data(mb, sizeof(*shm)); |
| + shared_handle = tipc_shared_handle_take(vds, shm->obj_id); |
| + if (shared_handle) { |
| + if (tipc_shared_handle_drop(shared_handle)) |
| + dev_err(dev, |
| + "Failed to drop handle found in dropped buffer"); |
| + } else { |
| + dev_err(dev, |
| + "Found handle in dropped buffer which was not registered to tipc device..."); |
| + } |
| + } |
| +} |
| + |
| +static void handle_dropped_mb(struct tipc_virtio_dev *vds, |
| + struct tipc_msg_buf *mb) |
| +{ |
| + struct tipc_msg_hdr *msg; |
| + |
| + mb_reset_read(mb); |
| + msg = mb_get_data(mb, sizeof(*msg)); |
| + if (msg->dst != TIPC_CTRL_ADDR) { |
| + handle_dropped_chan_msg(vds, mb, msg); |
| + } |
| +} |
| + |
| +static int _handle_rxbuf(struct tipc_virtio_dev *vds, |
| + struct tipc_msg_buf *rxbuf, size_t rxlen) |
| +{ |
| + int err; |
| + struct scatterlist sg; |
| + struct tipc_msg_hdr *msg; |
| + struct device *dev = &vds->vdev->dev; |
| + |
| + /* message sanity check */ |
| + if (rxlen > rxbuf->buf_sz) { |
| + dev_warn(dev, "inbound msg is too big: %zd\n", rxlen); |
| + goto drop_it; |
| + } |
| + |
| + if (rxlen < sizeof(*msg)) { |
| + dev_warn(dev, "inbound msg is too short: %zd\n", rxlen); |
| + goto drop_it; |
| + } |
| + |
| + /* reset buffer and put data */ |
| + mb_reset(rxbuf); |
| + mb_put_data(rxbuf, rxlen); |
| + |
| + /* get message header */ |
| + msg = mb_get_data(rxbuf, sizeof(*msg)); |
| + if (mb_avail_data(rxbuf) != msg->len) { |
| + dev_warn(dev, "inbound msg length mismatch: (%zu vs. %d)\n", |
| + mb_avail_data(rxbuf), msg->len); |
| + goto drop_it; |
| + } |
| + |
| + dev_dbg(dev, "From: %d, To: %d, Len: %d, Flags: 0x%x, Reserved: %d, shm_cnt: %d\n", |
| + msg->src, msg->dst, msg->len, msg->flags, msg->reserved, |
| + msg->shm_cnt); |
| + |
| + /* message directed to control endpoint is a special case */ |
| + if (msg->dst == TIPC_CTRL_ADDR) { |
| + _handle_ctrl_msg(vds, msg->data, msg->len, msg->src); |
| + } else { |
| + struct tipc_chan *chan = NULL; |
| + /* Lookup channel */ |
| + chan = vds_lookup_channel(vds, msg->dst); |
| + if (chan) { |
| + /* handle it */ |
| + rxbuf = chan->ops->handle_msg(chan->ops_arg, rxbuf); |
| + kref_put(&chan->refcount, _free_chan); |
| + if (WARN_ON(!rxbuf)) |
| + return -EINVAL; |
| + } |
| + } |
| + |
| +drop_it: |
| + /* add the buffer back to the virtqueue */ |
| + sg_init_one(&sg, rxbuf, rxbuf->buf_sz); |
| + err = virtqueue_add_inbuf(vds->rxvq, &sg, 1, rxbuf, GFP_KERNEL); |
| + if (err < 0) { |
| + dev_err(dev, "failed to add a virtqueue buffer: %d\n", err); |
| + return err; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static void _rxvq_cb(struct virtqueue *rxvq) |
| +{ |
| + unsigned int len; |
| + struct tipc_msg_buf *mb; |
| + unsigned int msg_cnt = 0; |
| + struct tipc_virtio_dev *vds = rxvq->vdev->priv; |
| + |
| + while ((mb = virtqueue_get_buf(rxvq, &len)) != NULL) { |
| + if (_handle_rxbuf(vds, mb, len)) |
| + break; |
| + msg_cnt++; |
| + } |
| + |
| + /* tell the other size that we added rx buffers */ |
| + if (msg_cnt) |
| + virtqueue_kick(rxvq); |
| +} |
| + |
| +static void _txvq_cb(struct virtqueue *txvq) |
| +{ |
| + unsigned int len; |
| + struct tipc_msg_buf *mb; |
| + bool need_wakeup = false; |
| + struct tipc_virtio_dev *vds = txvq->vdev->priv; |
| + |
| + /* detach all buffers */ |
| + mutex_lock(&vds->lock); |
| + while ((mb = virtqueue_get_buf(txvq, &len)) != NULL) { |
| + if ((int)len < 0) |
| + handle_dropped_mb(vds, mb); |
| + need_wakeup |= _put_txbuf_locked(vds, mb); |
| + } |
| + mutex_unlock(&vds->lock); |
| + |
| + if (need_wakeup) { |
| + /* wake up potential senders waiting for a tx buffer */ |
| + wake_up_interruptible_all(&vds->sendq); |
| + } |
| +} |
| + |
| +static int tipc_virtio_probe(struct virtio_device *vdev) |
| +{ |
| + int err, i; |
| + struct tipc_virtio_dev *vds; |
| + struct tipc_dev_config config; |
| + struct virtqueue *vqs[2]; |
| + vq_callback_t *vq_cbs[] = {_rxvq_cb, _txvq_cb}; |
| + static const char * const vq_names[] = { "rx", "tx" }; |
| + |
| + vds = kzalloc(sizeof(*vds), GFP_KERNEL); |
| + if (!vds) |
| + return -ENOMEM; |
| + |
| + vds->vdev = vdev; |
| + |
| + mutex_init(&vds->lock); |
| + mutex_init(&vds->shared_handles_lock); |
| + kref_init(&vds->refcount); |
| + init_waitqueue_head(&vds->sendq); |
| + INIT_LIST_HEAD(&vds->free_buf_list); |
| + idr_init(&vds->addr_idr); |
| + vds->shared_handles = RB_ROOT; |
| + dma_coerce_mask_and_coherent(&vds->vdev->dev, |
| + *vds->vdev->dev.parent->parent->dma_mask); |
| + |
| + /* set default max message size and alignment */ |
| + memset(&config, 0, sizeof(config)); |
| + config.msg_buf_max_size = DEFAULT_MSG_BUF_SIZE; |
| + config.msg_buf_alignment = DEFAULT_MSG_BUF_ALIGN; |
| + |
| + /* get configuration if present */ |
| + vdev->config->get(vdev, 0, &config, sizeof(config)); |
| + |
| + /* copy dev name */ |
| + strncpy(vds->cdev_name, config.dev_name, sizeof(vds->cdev_name)); |
| + vds->cdev_name[sizeof(vds->cdev_name)-1] = '\0'; |
| + |
| + /* find tx virtqueues (rx and tx and in this order) */ |
| + err = vdev->config->find_vqs(vdev, 2, vqs, vq_cbs, vq_names, NULL, |
| + NULL); |
| + if (err) |
| + goto err_find_vqs; |
| + |
| + vds->rxvq = vqs[0]; |
| + vds->txvq = vqs[1]; |
| + |
| + /* save max buffer size and count */ |
| + vds->msg_buf_max_sz = config.msg_buf_max_size; |
| + vds->msg_buf_max_cnt = virtqueue_get_vring_size(vds->txvq); |
| + |
| + /* set up the receive buffers */ |
| + for (i = 0; i < virtqueue_get_vring_size(vds->rxvq); i++) { |
| + struct scatterlist sg; |
| + struct tipc_msg_buf *rxbuf; |
| + |
| + rxbuf = vds_alloc_msg_buf(vds, true); |
| + if (!rxbuf) { |
| + dev_err(&vdev->dev, "failed to allocate rx buffer\n"); |
| + err = -ENOMEM; |
| + goto err_free_rx_buffers; |
| + } |
| + |
| + sg_init_one(&sg, rxbuf, rxbuf->buf_sz); |
| + err = virtqueue_add_inbuf(vds->rxvq, &sg, 1, rxbuf, GFP_KERNEL); |
| + WARN_ON(err); /* sanity check; this can't really happen */ |
| + } |
| + |
| + vdev->priv = vds; |
| + vds->state = VDS_OFFLINE; |
| + |
| + dev_dbg(&vdev->dev, "%s: done\n", __func__); |
| + return 0; |
| + |
| +err_free_rx_buffers: |
| + _cleanup_vq(vds, vds->rxvq); |
| +err_find_vqs: |
| + kref_put(&vds->refcount, _free_vds); |
| + return err; |
| +} |
| + |
| +static void tipc_virtio_remove(struct virtio_device *vdev) |
| +{ |
| + struct tipc_virtio_dev *vds = vdev->priv; |
| + |
| + _go_offline(vds); |
| + |
| + mutex_lock(&vds->lock); |
| + vds->state = VDS_DEAD; |
| + vds->vdev = NULL; |
| + mutex_unlock(&vds->lock); |
| + |
| + vdev->config->reset(vdev); |
| + |
| + idr_destroy(&vds->addr_idr); |
| + |
| + _cleanup_vq(vds, vds->rxvq); |
| + _cleanup_vq(vds, vds->txvq); |
| + vds_free_msg_buf_list(vds, &vds->free_buf_list); |
| + |
| + vdev->config->del_vqs(vds->vdev); |
| + |
| + kref_put(&vds->refcount, _free_vds); |
| +} |
| + |
| +static const struct virtio_device_id tipc_virtio_id_table[] = { |
| + { VIRTIO_ID_TRUSTY_IPC, VIRTIO_DEV_ANY_ID }, |
| + { 0 }, |
| +}; |
| + |
| +static const unsigned int features[] = { |
| + 0, |
| +}; |
| + |
| +static struct virtio_driver virtio_tipc_driver = { |
| + .feature_table = features, |
| + .feature_table_size = ARRAY_SIZE(features), |
| + .driver.name = KBUILD_MODNAME, |
| + .driver.owner = THIS_MODULE, |
| + .id_table = tipc_virtio_id_table, |
| + .probe = tipc_virtio_probe, |
| + .remove = tipc_virtio_remove, |
| +}; |
| + |
| +static int __init tipc_init(void) |
| +{ |
| + int ret; |
| + dev_t dev; |
| + |
| + ret = alloc_chrdev_region(&dev, 0, MAX_DEVICES, KBUILD_MODNAME); |
| + if (ret) { |
| + pr_err("%s: alloc_chrdev_region failed: %d\n", __func__, ret); |
| + return ret; |
| + } |
| + |
| + tipc_major = MAJOR(dev); |
| + tipc_class = class_create(THIS_MODULE, KBUILD_MODNAME); |
| + if (IS_ERR(tipc_class)) { |
| + ret = PTR_ERR(tipc_class); |
| + pr_err("%s: class_create failed: %d\n", __func__, ret); |
| + goto err_class_create; |
| + } |
| + |
| + ret = register_virtio_driver(&virtio_tipc_driver); |
| + if (ret) { |
| + pr_err("failed to register virtio driver: %d\n", ret); |
| + goto err_register_virtio_drv; |
| + } |
| + |
| + return 0; |
| + |
| +err_register_virtio_drv: |
| + class_destroy(tipc_class); |
| + |
| +err_class_create: |
| + unregister_chrdev_region(dev, MAX_DEVICES); |
| + return ret; |
| +} |
| + |
| +static void __exit tipc_exit(void) |
| +{ |
| + unregister_virtio_driver(&virtio_tipc_driver); |
| + class_destroy(tipc_class); |
| + unregister_chrdev_region(MKDEV(tipc_major, 0), MAX_DEVICES); |
| +} |
| + |
| +/* We need to init this early */ |
| +subsys_initcall(tipc_init); |
| +module_exit(tipc_exit); |
| + |
| +MODULE_DEVICE_TABLE(tipc, tipc_virtio_id_table); |
| +MODULE_DESCRIPTION("Trusty IPC driver"); |
| +MODULE_LICENSE("GPL v2"); |
| diff --git a/drivers/trusty/trusty-irq.c b/drivers/trusty/trusty-irq.c |
| new file mode 100644 |
| index 000000000000..5c6076108d0e |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-irq.c |
| @@ -0,0 +1,645 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2013 Google, Inc. |
| + */ |
| + |
| +#include <linux/cpu.h> |
| +#include <linux/interrupt.h> |
| +#include <linux/irq.h> |
| +#include <linux/irqdomain.h> |
| +#include <linux/module.h> |
| +#include <linux/of.h> |
| +#include <linux/of_irq.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/slab.h> |
| +#include <linux/string.h> |
| +#include <linux/trusty/smcall.h> |
| +#include <linux/trusty/sm_err.h> |
| +#include <linux/trusty/trusty.h> |
| + |
| +struct trusty_irq { |
| + struct trusty_irq_state *is; |
| + struct hlist_node node; |
| + unsigned int irq; |
| + bool percpu; |
| + bool enable; |
| + bool doorbell; |
| + struct trusty_irq __percpu *percpu_ptr; |
| +}; |
| + |
| +struct trusty_irq_irqset { |
| + struct hlist_head pending; |
| + struct hlist_head inactive; |
| +}; |
| + |
| +struct trusty_irq_state { |
| + struct device *dev; |
| + struct device *trusty_dev; |
| + struct trusty_irq_irqset normal_irqs; |
| + spinlock_t normal_irqs_lock; |
| + struct trusty_irq_irqset __percpu *percpu_irqs; |
| + struct notifier_block trusty_call_notifier; |
| + struct hlist_node cpuhp_node; |
| +}; |
| + |
| +static int trusty_irq_cpuhp_slot = -1; |
| + |
| +static void trusty_irq_enable_pending_irqs(struct trusty_irq_state *is, |
| + struct trusty_irq_irqset *irqset, |
| + bool percpu) |
| +{ |
| + struct hlist_node *n; |
| + struct trusty_irq *trusty_irq; |
| + |
| + hlist_for_each_entry_safe(trusty_irq, n, &irqset->pending, node) { |
| + dev_dbg(is->dev, |
| + "%s: enable pending irq %d, percpu %d, cpu %d\n", |
| + __func__, trusty_irq->irq, percpu, smp_processor_id()); |
| + if (percpu) |
| + enable_percpu_irq(trusty_irq->irq, 0); |
| + else |
| + enable_irq(trusty_irq->irq); |
| + hlist_del(&trusty_irq->node); |
| + hlist_add_head(&trusty_irq->node, &irqset->inactive); |
| + } |
| +} |
| + |
| +static void trusty_irq_enable_irqset(struct trusty_irq_state *is, |
| + struct trusty_irq_irqset *irqset) |
| +{ |
| + struct trusty_irq *trusty_irq; |
| + |
| + hlist_for_each_entry(trusty_irq, &irqset->inactive, node) { |
| + if (trusty_irq->enable) { |
| + dev_warn(is->dev, |
| + "%s: percpu irq %d already enabled, cpu %d\n", |
| + __func__, trusty_irq->irq, smp_processor_id()); |
| + continue; |
| + } |
| + dev_dbg(is->dev, "%s: enable percpu irq %d, cpu %d\n", |
| + __func__, trusty_irq->irq, smp_processor_id()); |
| + enable_percpu_irq(trusty_irq->irq, 0); |
| + trusty_irq->enable = true; |
| + } |
| +} |
| + |
| +static void trusty_irq_disable_irqset(struct trusty_irq_state *is, |
| + struct trusty_irq_irqset *irqset) |
| +{ |
| + struct hlist_node *n; |
| + struct trusty_irq *trusty_irq; |
| + |
| + hlist_for_each_entry(trusty_irq, &irqset->inactive, node) { |
| + if (!trusty_irq->enable) { |
| + dev_warn(is->dev, |
| + "irq %d already disabled, percpu %d, cpu %d\n", |
| + trusty_irq->irq, trusty_irq->percpu, |
| + smp_processor_id()); |
| + continue; |
| + } |
| + dev_dbg(is->dev, "%s: disable irq %d, percpu %d, cpu %d\n", |
| + __func__, trusty_irq->irq, trusty_irq->percpu, |
| + smp_processor_id()); |
| + trusty_irq->enable = false; |
| + if (trusty_irq->percpu) |
| + disable_percpu_irq(trusty_irq->irq); |
| + else |
| + disable_irq_nosync(trusty_irq->irq); |
| + } |
| + hlist_for_each_entry_safe(trusty_irq, n, &irqset->pending, node) { |
| + if (!trusty_irq->enable) { |
| + dev_warn(is->dev, |
| + "pending irq %d already disabled, percpu %d, cpu %d\n", |
| + trusty_irq->irq, trusty_irq->percpu, |
| + smp_processor_id()); |
| + } |
| + dev_dbg(is->dev, |
| + "%s: disable pending irq %d, percpu %d, cpu %d\n", |
| + __func__, trusty_irq->irq, trusty_irq->percpu, |
| + smp_processor_id()); |
| + trusty_irq->enable = false; |
| + hlist_del(&trusty_irq->node); |
| + hlist_add_head(&trusty_irq->node, &irqset->inactive); |
| + } |
| +} |
| + |
| +static int trusty_irq_call_notify(struct notifier_block *nb, |
| + unsigned long action, void *data) |
| +{ |
| + struct trusty_irq_state *is; |
| + |
| + if (WARN_ON(!irqs_disabled())) |
| + return NOTIFY_DONE; |
| + |
| + if (action != TRUSTY_CALL_PREPARE) |
| + return NOTIFY_DONE; |
| + |
| + is = container_of(nb, struct trusty_irq_state, trusty_call_notifier); |
| + |
| + spin_lock(&is->normal_irqs_lock); |
| + trusty_irq_enable_pending_irqs(is, &is->normal_irqs, false); |
| + spin_unlock(&is->normal_irqs_lock); |
| + trusty_irq_enable_pending_irqs(is, this_cpu_ptr(is->percpu_irqs), true); |
| + |
| + return NOTIFY_OK; |
| +} |
| + |
| +static irqreturn_t trusty_irq_handler(int irq, void *data) |
| +{ |
| + struct trusty_irq *trusty_irq = data; |
| + struct trusty_irq_state *is = trusty_irq->is; |
| + struct trusty_irq_irqset *irqset; |
| + |
| + dev_dbg(is->dev, "%s: irq %d, percpu %d, cpu %d, enable %d\n", |
| + __func__, irq, trusty_irq->irq, smp_processor_id(), |
| + trusty_irq->enable); |
| + |
| + if (!trusty_irq->doorbell) { |
| + if (trusty_irq->percpu) { |
| + disable_percpu_irq(irq); |
| + irqset = this_cpu_ptr(is->percpu_irqs); |
| + } else { |
| + disable_irq_nosync(irq); |
| + irqset = &is->normal_irqs; |
| + } |
| + |
| + spin_lock(&is->normal_irqs_lock); |
| + if (trusty_irq->enable) { |
| + hlist_del(&trusty_irq->node); |
| + hlist_add_head(&trusty_irq->node, &irqset->pending); |
| + } |
| + spin_unlock(&is->normal_irqs_lock); |
| + } |
| + |
| + trusty_enqueue_nop(is->trusty_dev, NULL); |
| + |
| + dev_dbg(is->dev, "%s: irq %d done\n", __func__, irq); |
| + |
| + return IRQ_HANDLED; |
| +} |
| + |
| +static int trusty_irq_cpu_up(unsigned int cpu, struct hlist_node *node) |
| +{ |
| + unsigned long irq_flags; |
| + struct trusty_irq_state *is; |
| + |
| + is = container_of(node, struct trusty_irq_state, cpuhp_node); |
| + |
| + dev_dbg(is->dev, "%s: cpu %d\n", __func__, cpu); |
| + |
| + local_irq_save(irq_flags); |
| + trusty_irq_enable_irqset(is, this_cpu_ptr(is->percpu_irqs)); |
| + local_irq_restore(irq_flags); |
| + |
| + /* |
| + * Temporary workaround blindly enqueuing work to force trusty scheduler |
| + * to run after a cpu suspend. |
| + * Root causing the workqueue being inappropriately empty |
| + * (e.g. loss of an IPI) may make this workaround unnecessary |
| + * in the future. |
| + */ |
| + trusty_enqueue_nop(is->trusty_dev, NULL); |
| + |
| + return 0; |
| +} |
| + |
| +static int trusty_irq_cpu_down(unsigned int cpu, struct hlist_node *node) |
| +{ |
| + unsigned long irq_flags; |
| + struct trusty_irq_state *is; |
| + |
| + is = container_of(node, struct trusty_irq_state, cpuhp_node); |
| + |
| + dev_dbg(is->dev, "%s: cpu %d\n", __func__, cpu); |
| + |
| + local_irq_save(irq_flags); |
| + trusty_irq_disable_irqset(is, this_cpu_ptr(is->percpu_irqs)); |
| + local_irq_restore(irq_flags); |
| + |
| + return 0; |
| +} |
| + |
| +static int trusty_irq_map_ipi(struct trusty_irq_state *is, int irq) |
| +{ |
| + int ret; |
| + u32 ipi_range[3]; |
| + struct device_node *gic; |
| + struct of_phandle_args oirq = {}; |
| + u32 beg, end, ipi_base; |
| + |
| + ret = of_property_read_u32_array(is->dev->of_node, "ipi-range", |
| + ipi_range, ARRAY_SIZE(ipi_range)); |
| + if (ret != 0) |
| + return -ENODATA; |
| + beg = ipi_range[0]; |
| + end = ipi_range[1]; |
| + ipi_base = ipi_range[2]; |
| + |
| + if (irq < beg || irq > end) |
| + return -ENODATA; |
| + |
| + gic = of_irq_find_parent(is->dev->of_node); |
| + if (!gic) |
| + return -ENXIO; |
| + |
| + oirq.np = gic; |
| + oirq.args_count = 1; |
| + oirq.args[0] = ipi_base + (irq - beg); |
| + |
| + ret = irq_create_of_mapping(&oirq); |
| + |
| + of_node_put(gic); |
| + return (!ret) ? -EINVAL : ret; |
| +} |
| + |
| +static int trusty_irq_create_irq_mapping(struct trusty_irq_state *is, int irq) |
| +{ |
| + int ret; |
| + int index; |
| + u32 irq_pos; |
| + u32 templ_idx; |
| + u32 range_base; |
| + u32 range_end; |
| + struct of_phandle_args oirq; |
| + |
| + /* check if this is an IPI (inter-processor interrupt) */ |
| + ret = trusty_irq_map_ipi(is, irq); |
| + if (ret != -ENODATA) |
| + return ret; |
| + |
| + /* check if "interrupt-ranges" property is present */ |
| + if (!of_find_property(is->dev->of_node, "interrupt-ranges", NULL)) { |
| + /* fallback to old behavior to be backward compatible with |
| + * systems that do not need IRQ domains. |
| + */ |
| + return irq; |
| + } |
| + |
| + /* find irq range */ |
| + for (index = 0;; index += 3) { |
| + ret = of_property_read_u32_index(is->dev->of_node, |
| + "interrupt-ranges", |
| + index, &range_base); |
| + if (ret) |
| + return ret; |
| + |
| + ret = of_property_read_u32_index(is->dev->of_node, |
| + "interrupt-ranges", |
| + index + 1, &range_end); |
| + if (ret) |
| + return ret; |
| + |
| + if (irq >= range_base && irq <= range_end) |
| + break; |
| + } |
| + |
| + /* read the rest of range entry: template index and irq_pos */ |
| + ret = of_property_read_u32_index(is->dev->of_node, |
| + "interrupt-ranges", |
| + index + 2, &templ_idx); |
| + if (ret) |
| + return ret; |
| + |
| + /* read irq template */ |
| + ret = of_parse_phandle_with_args(is->dev->of_node, |
| + "interrupt-templates", |
| + "#interrupt-cells", |
| + templ_idx, &oirq); |
| + if (ret) |
| + return ret; |
| + |
| + WARN_ON(!oirq.np); |
| + WARN_ON(!oirq.args_count); |
| + |
| + /* |
| + * An IRQ template is a non empty array of u32 values describing group |
| + * of interrupts having common properties. The u32 entry with index |
| + * zero contains the position of irq_id in interrupt specifier array |
| + * followed by data representing interrupt specifier array with irq id |
| + * field omitted, so to convert irq template to interrupt specifier |
| + * array we have to move down one slot the first irq_pos entries and |
| + * replace the resulting gap with real irq id. |
| + */ |
| + irq_pos = oirq.args[0]; |
| + |
| + if (irq_pos >= oirq.args_count) { |
| + dev_err(is->dev, "irq pos is out of range: %d\n", irq_pos); |
| + return -EINVAL; |
| + } |
| + |
| + for (index = 1; index <= irq_pos; index++) |
| + oirq.args[index - 1] = oirq.args[index]; |
| + |
| + oirq.args[irq_pos] = irq - range_base; |
| + |
| + ret = irq_create_of_mapping(&oirq); |
| + |
| + return (!ret) ? -EINVAL : ret; |
| +} |
| + |
| +static int trusty_irq_init_normal_irq(struct trusty_irq_state *is, int tirq) |
| +{ |
| + int ret; |
| + int irq; |
| + unsigned long irq_flags; |
| + struct trusty_irq *trusty_irq; |
| + |
| + dev_dbg(is->dev, "%s: irq %d\n", __func__, tirq); |
| + |
| + irq = trusty_irq_create_irq_mapping(is, tirq); |
| + if (irq < 0) { |
| + dev_err(is->dev, |
| + "trusty_irq_create_irq_mapping failed (%d)\n", irq); |
| + return irq; |
| + } |
| + |
| + trusty_irq = kzalloc(sizeof(*trusty_irq), GFP_KERNEL); |
| + if (!trusty_irq) |
| + return -ENOMEM; |
| + |
| + trusty_irq->is = is; |
| + trusty_irq->irq = irq; |
| + trusty_irq->enable = true; |
| + |
| + spin_lock_irqsave(&is->normal_irqs_lock, irq_flags); |
| + hlist_add_head(&trusty_irq->node, &is->normal_irqs.inactive); |
| + spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags); |
| + |
| + ret = request_irq(irq, trusty_irq_handler, IRQF_NO_THREAD, |
| + "trusty", trusty_irq); |
| + if (ret) { |
| + dev_err(is->dev, "request_irq failed %d\n", ret); |
| + goto err_request_irq; |
| + } |
| + return 0; |
| + |
| +err_request_irq: |
| + spin_lock_irqsave(&is->normal_irqs_lock, irq_flags); |
| + hlist_del(&trusty_irq->node); |
| + spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags); |
| + kfree(trusty_irq); |
| + return ret; |
| +} |
| + |
| +static int trusty_irq_init_per_cpu_irq(struct trusty_irq_state *is, int tirq, |
| + unsigned int type) |
| +{ |
| + int ret; |
| + int irq; |
| + unsigned int cpu; |
| + struct trusty_irq __percpu *trusty_irq_handler_data; |
| + |
| + dev_dbg(is->dev, "%s: irq %d\n", __func__, tirq); |
| + |
| + irq = trusty_irq_create_irq_mapping(is, tirq); |
| + if (irq <= 0) { |
| + dev_err(is->dev, |
| + "trusty_irq_create_irq_mapping failed (%d)\n", irq); |
| + return irq; |
| + } |
| + |
| + trusty_irq_handler_data = alloc_percpu(struct trusty_irq); |
| + if (!trusty_irq_handler_data) |
| + return -ENOMEM; |
| + |
| + for_each_possible_cpu(cpu) { |
| + struct trusty_irq *trusty_irq; |
| + struct trusty_irq_irqset *irqset; |
| + |
| + trusty_irq = per_cpu_ptr(trusty_irq_handler_data, cpu); |
| + irqset = per_cpu_ptr(is->percpu_irqs, cpu); |
| + |
| + trusty_irq->is = is; |
| + hlist_add_head(&trusty_irq->node, &irqset->inactive); |
| + trusty_irq->irq = irq; |
| + trusty_irq->percpu = true; |
| + trusty_irq->doorbell = type == TRUSTY_IRQ_TYPE_DOORBELL; |
| + trusty_irq->percpu_ptr = trusty_irq_handler_data; |
| + } |
| + |
| + ret = request_percpu_irq(irq, trusty_irq_handler, "trusty", |
| + trusty_irq_handler_data); |
| + if (ret) { |
| + dev_err(is->dev, "request_percpu_irq failed %d\n", ret); |
| + goto err_request_percpu_irq; |
| + } |
| + |
| + return 0; |
| + |
| +err_request_percpu_irq: |
| + for_each_possible_cpu(cpu) { |
| + struct trusty_irq *trusty_irq; |
| + |
| + trusty_irq = per_cpu_ptr(trusty_irq_handler_data, cpu); |
| + hlist_del(&trusty_irq->node); |
| + } |
| + |
| + free_percpu(trusty_irq_handler_data); |
| + return ret; |
| +} |
| + |
| +static int trusty_smc_get_next_irq(struct trusty_irq_state *is, |
| + unsigned long min_irq, unsigned int type) |
| +{ |
| + return trusty_fast_call32(is->trusty_dev, SMC_FC_GET_NEXT_IRQ, |
| + min_irq, type, 0); |
| +} |
| + |
| +static int trusty_irq_init_one(struct trusty_irq_state *is, |
| + int irq, unsigned int type) |
| +{ |
| + int ret; |
| + |
| + irq = trusty_smc_get_next_irq(is, irq, type); |
| + if (irq < 0) |
| + return irq; |
| + |
| + if (type != TRUSTY_IRQ_TYPE_NORMAL) |
| + ret = trusty_irq_init_per_cpu_irq(is, irq, type); |
| + else |
| + ret = trusty_irq_init_normal_irq(is, irq); |
| + |
| + if (ret) { |
| + dev_warn(is->dev, |
| + "failed to initialize irq %d, irq will be ignored\n", |
| + irq); |
| + } |
| + |
| + return irq + 1; |
| +} |
| + |
| +static void trusty_irq_free_irqs(struct trusty_irq_state *is) |
| +{ |
| + struct trusty_irq *irq; |
| + struct hlist_node *n; |
| + unsigned int cpu; |
| + |
| + hlist_for_each_entry_safe(irq, n, &is->normal_irqs.inactive, node) { |
| + dev_dbg(is->dev, "%s: irq %d\n", __func__, irq->irq); |
| + free_irq(irq->irq, irq); |
| + hlist_del(&irq->node); |
| + kfree(irq); |
| + } |
| + hlist_for_each_entry_safe(irq, n, |
| + &this_cpu_ptr(is->percpu_irqs)->inactive, |
| + node) { |
| + struct trusty_irq __percpu *trusty_irq_handler_data; |
| + |
| + dev_dbg(is->dev, "%s: percpu irq %d\n", __func__, irq->irq); |
| + trusty_irq_handler_data = irq->percpu_ptr; |
| + free_percpu_irq(irq->irq, trusty_irq_handler_data); |
| + for_each_possible_cpu(cpu) { |
| + struct trusty_irq *irq_tmp; |
| + |
| + irq_tmp = per_cpu_ptr(trusty_irq_handler_data, cpu); |
| + hlist_del(&irq_tmp->node); |
| + } |
| + free_percpu(trusty_irq_handler_data); |
| + } |
| +} |
| + |
| +static int trusty_irq_probe(struct platform_device *pdev) |
| +{ |
| + int ret; |
| + int irq; |
| + unsigned long irq_flags; |
| + struct trusty_irq_state *is; |
| + |
| + is = kzalloc(sizeof(*is), GFP_KERNEL); |
| + if (!is) { |
| + ret = -ENOMEM; |
| + goto err_alloc_is; |
| + } |
| + |
| + is->dev = &pdev->dev; |
| + is->trusty_dev = is->dev->parent; |
| + spin_lock_init(&is->normal_irqs_lock); |
| + is->percpu_irqs = alloc_percpu(struct trusty_irq_irqset); |
| + if (!is->percpu_irqs) { |
| + ret = -ENOMEM; |
| + goto err_alloc_pending_percpu_irqs; |
| + } |
| + |
| + platform_set_drvdata(pdev, is); |
| + |
| + is->trusty_call_notifier.notifier_call = trusty_irq_call_notify; |
| + ret = trusty_call_notifier_register(is->trusty_dev, |
| + &is->trusty_call_notifier); |
| + if (ret) { |
| + dev_err(&pdev->dev, |
| + "failed to register trusty call notifier\n"); |
| + goto err_trusty_call_notifier_register; |
| + } |
| + |
| + for (irq = 0; irq >= 0;) |
| + irq = trusty_irq_init_one(is, irq, TRUSTY_IRQ_TYPE_PER_CPU); |
| + for (irq = 0; irq >= 0;) |
| + irq = trusty_irq_init_one(is, irq, TRUSTY_IRQ_TYPE_NORMAL); |
| + for (irq = 0; irq >= 0;) |
| + irq = trusty_irq_init_one(is, irq, TRUSTY_IRQ_TYPE_DOORBELL); |
| + |
| + ret = cpuhp_state_add_instance(trusty_irq_cpuhp_slot, &is->cpuhp_node); |
| + if (ret < 0) { |
| + dev_err(&pdev->dev, "cpuhp_state_add_instance failed %d\n", |
| + ret); |
| + goto err_add_cpuhp_instance; |
| + } |
| + |
| + return 0; |
| + |
| +err_add_cpuhp_instance: |
| + spin_lock_irqsave(&is->normal_irqs_lock, irq_flags); |
| + trusty_irq_disable_irqset(is, &is->normal_irqs); |
| + spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags); |
| + trusty_irq_free_irqs(is); |
| + trusty_call_notifier_unregister(is->trusty_dev, |
| + &is->trusty_call_notifier); |
| +err_trusty_call_notifier_register: |
| + free_percpu(is->percpu_irqs); |
| +err_alloc_pending_percpu_irqs: |
| + kfree(is); |
| +err_alloc_is: |
| + return ret; |
| +} |
| + |
| +static int trusty_irq_remove(struct platform_device *pdev) |
| +{ |
| + int ret; |
| + unsigned long irq_flags; |
| + struct trusty_irq_state *is = platform_get_drvdata(pdev); |
| + |
| + ret = cpuhp_state_remove_instance(trusty_irq_cpuhp_slot, |
| + &is->cpuhp_node); |
| + if (WARN_ON(ret)) |
| + return ret; |
| + |
| + spin_lock_irqsave(&is->normal_irqs_lock, irq_flags); |
| + trusty_irq_disable_irqset(is, &is->normal_irqs); |
| + spin_unlock_irqrestore(&is->normal_irqs_lock, irq_flags); |
| + |
| + trusty_irq_free_irqs(is); |
| + |
| + trusty_call_notifier_unregister(is->trusty_dev, |
| + &is->trusty_call_notifier); |
| + free_percpu(is->percpu_irqs); |
| + kfree(is); |
| + |
| + return 0; |
| +} |
| + |
| +static const struct of_device_id trusty_test_of_match[] = { |
| + { .compatible = "android,trusty-irq-v1", }, |
| + {}, |
| +}; |
| + |
| +MODULE_DEVICE_TABLE(trusty, trusty_test_of_match); |
| + |
| +static struct platform_driver trusty_irq_driver = { |
| + .probe = trusty_irq_probe, |
| + .remove = trusty_irq_remove, |
| + .driver = { |
| + .name = "trusty-irq", |
| + .of_match_table = trusty_test_of_match, |
| + }, |
| +}; |
| + |
| +static int __init trusty_irq_driver_init(void) |
| +{ |
| + int ret; |
| + |
| + /* allocate dynamic cpuhp state slot */ |
| + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, |
| + "trusty-irq:cpu:online", |
| + trusty_irq_cpu_up, |
| + trusty_irq_cpu_down); |
| + if (ret < 0) |
| + return ret; |
| + trusty_irq_cpuhp_slot = ret; |
| + |
| + /* Register platform driver */ |
| + ret = platform_driver_register(&trusty_irq_driver); |
| + if (ret < 0) |
| + goto err_driver_register; |
| + |
| + return ret; |
| + |
| +err_driver_register: |
| + /* undo cpuhp slot allocation */ |
| + cpuhp_remove_multi_state(trusty_irq_cpuhp_slot); |
| + trusty_irq_cpuhp_slot = -1; |
| + |
| + return ret; |
| +} |
| + |
| +static void __exit trusty_irq_driver_exit(void) |
| +{ |
| + platform_driver_unregister(&trusty_irq_driver); |
| + cpuhp_remove_multi_state(trusty_irq_cpuhp_slot); |
| + trusty_irq_cpuhp_slot = -1; |
| +} |
| + |
| +module_init(trusty_irq_driver_init); |
| +module_exit(trusty_irq_driver_exit); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_DESCRIPTION("Trusty IRQ driver"); |
| diff --git a/drivers/trusty/trusty-log.c b/drivers/trusty/trusty-log.c |
| new file mode 100644 |
| index 000000000000..7b279fe63766 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-log.c |
| @@ -0,0 +1,830 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2015 Google, Inc. |
| + */ |
| +#include <linux/platform_device.h> |
| +#include <linux/trusty/smcall.h> |
| +#include <linux/trusty/trusty.h> |
| +#include <linux/notifier.h> |
| +#include <linux/scatterlist.h> |
| +#include <linux/slab.h> |
| +#include <linux/mm.h> |
| +#include <linux/mod_devicetable.h> |
| +#include <linux/module.h> |
| +#include <linux/moduleparam.h> |
| +#include <linux/log2.h> |
| +#include <linux/miscdevice.h> |
| +#include <linux/poll.h> |
| +#include <linux/seq_file.h> |
| +#include <asm/page.h> |
| +#include "trusty-log.h" |
| + |
| +/* |
| + * Rationale for the chosen default log buffer size: |
| + * - the log buffer shall contain unthrottled Trusty crash dump. |
| + * - the register list portion of a crash dump is about 1KB |
| + * - the memory-around-registers portion of a crash dump can be up to 12 KB |
| + * - an average size backtrace is about 1 KB |
| + * - average length of non-crash trusty logs during boot is about 85 characters |
| + * - a crash dump with 50 lines of context therefore requires up to 18 KB |
| + * - buffer size needs to be power-of-two number of bytes |
| + * - rounding up to power of two from 18 KB gives 32 KB |
| + * The log size can be adjusted by setting the "trusty_log.log_size" parameter |
| + * on the kernel command line. The specified value will be adjusted as needed. |
| + */ |
| + |
| +#define TRUSTY_LOG_DEFAULT_SIZE (32768) |
| +#define TRUSTY_LOG_MIN_SIZE (PAGE_SIZE / 2) |
| +#define TRUSTY_LOG_MAX_SIZE (1 * 1024 * 1024 * 1024) |
| +#define TRUSTY_LINE_BUFFER_SIZE (256) |
| + |
| +static size_t log_size_param = TRUSTY_LOG_DEFAULT_SIZE; |
| + |
| +static int trusty_log_size_set(const char *val, const struct kernel_param *kp) |
| +{ |
| + unsigned long long requested = memparse(val, NULL); |
| + |
| + if (requested < TRUSTY_LOG_MIN_SIZE) |
| + requested = TRUSTY_LOG_MIN_SIZE; |
| + if (requested > TRUSTY_LOG_MAX_SIZE) |
| + requested = TRUSTY_LOG_MAX_SIZE; |
| + requested = rounddown_pow_of_two(requested); |
| + log_size_param = requested; |
| + return 0; |
| +} |
| + |
| +static int trusty_log_size_get(char *buffer, const struct kernel_param *kp) |
| +{ |
| + sprintf(buffer, "%zu", log_size_param); |
| + return strlen(buffer); |
| +} |
| + |
| +module_param_call(log_size, trusty_log_size_set, trusty_log_size_get, NULL, |
| + 0644); |
| +/* |
| + * If we log too much and a UART or other slow source is connected, we can stall |
| + * out another thread which is doing printk. |
| + * |
| + * Trusty crash logs are currently ~16 lines, so 100 should include context and |
| + * the crash most of the time. |
| + */ |
| +static struct ratelimit_state trusty_log_rate_limit = |
| + RATELIMIT_STATE_INIT("trusty_log", 1 * HZ, 100); |
| + |
| +/** |
| + * struct trusty_log_sfile - trusty log misc device state |
| + * |
| + * @misc: misc device created for the trusty log virtual file |
| + * @device_name: misc device name following the convention |
| + * "trusty-<name><id>" |
| + */ |
| +struct trusty_log_sfile { |
| + struct miscdevice misc; |
| + char device_name[64]; |
| +}; |
| + |
| +/** |
| + * struct trusty_log_sink_state - trusty log sink state |
| + * |
| + * @get: current read unwrapped index |
| + * @trusty_panicked: trusty panic status at the start of the sink interation |
| + * (only used for kernel log sink) |
| + * @sfile: seq_file used for sinking to a virtual file (misc device); |
| + * set to NULL for the kernel log sink. |
| + * @ignore_overflow: ignore_overflow used to coalesce overflow messages and |
| + * avoid reporting an overflow when sinking the oldest |
| + * line to the virtual file (only used for virtual file sink) |
| + * |
| + * A sink state structure is used for both the kernel log sink |
| + * and the virtual device sink. |
| + * An instance of the sink state structure is dynamically created |
| + * for each read iteration of the trusty log virtual file (misc device). |
| + * |
| + */ |
| +struct trusty_log_sink_state { |
| + u32 get; |
| + bool trusty_panicked; |
| + |
| + /* virtual file sink specific attributes */ |
| + struct seq_file *sfile; |
| + bool ignore_overflow; |
| +}; |
| + |
| +struct trusty_log_state { |
| + struct device *dev; |
| + struct device *trusty_dev; |
| + struct trusty_log_sfile log_sfile; |
| + |
| + struct log_rb *log; |
| + struct trusty_log_sink_state klog_sink; |
| + |
| + u32 log_num_pages; |
| + struct scatterlist *sg; |
| + trusty_shared_mem_id_t log_pages_shared_mem_id; |
| + |
| + struct notifier_block call_notifier; |
| + struct notifier_block panic_notifier; |
| + char line_buffer[TRUSTY_LINE_BUFFER_SIZE]; |
| + wait_queue_head_t poll_waiters; |
| + /* this lock protects access to wake_put */ |
| + spinlock_t wake_up_lock; |
| + u32 last_wake_put; |
| +}; |
| + |
| +static inline u32 u32_add_overflow(u32 a, u32 b) |
| +{ |
| + u32 d; |
| + |
| + if (check_add_overflow(a, b, &d)) { |
| + /* |
| + * silence the overflow, |
| + * what matters in the log buffer context |
| + * is the casted addition |
| + */ |
| + } |
| + return d; |
| +} |
| + |
| +static inline u32 u32_sub_overflow(u32 a, u32 b) |
| +{ |
| + u32 d; |
| + |
| + if (check_sub_overflow(a, b, &d)) { |
| + /* |
| + * silence the overflow, |
| + * what matters in the log buffer context |
| + * is the casted substraction |
| + */ |
| + } |
| + return d; |
| +} |
| + |
| +static int log_read_line(struct trusty_log_state *s, u32 put, u32 get) |
| +{ |
| + struct log_rb *log = s->log; |
| + int i; |
| + char c = '\0'; |
| + size_t max_to_read = |
| + min_t(size_t, |
| + u32_sub_overflow(put, get), |
| + sizeof(s->line_buffer) - 1); |
| + size_t mask = log->sz - 1; |
| + |
| + for (i = 0; i < max_to_read && c != '\n';) { |
| + c = log->data[get & mask]; |
| + s->line_buffer[i++] = c; |
| + get = u32_add_overflow(get, 1); |
| + } |
| + s->line_buffer[i] = '\0'; |
| + |
| + return i; |
| +} |
| + |
| +/** |
| + * trusty_log_has_data() - returns true when more data is available to sink |
| + * @s: Current log state. |
| + * @sink: trusty_log_sink_state holding the get index on a given sink |
| + * |
| + * Return: true if data is available. |
| + */ |
| +static bool trusty_log_has_data(struct trusty_log_state *s, |
| + struct trusty_log_sink_state *sink) |
| +{ |
| + struct log_rb *log = s->log; |
| + |
| + return (log->put != sink->get); |
| +} |
| + |
| +/** |
| + * trusty_log_start() - initialize the sink iteration either to kernel log |
| + * or to secondary log_sfile |
| + * @s: Current log state. |
| + * @sink: trusty_log_sink_state holding the get index on a given sink |
| + * @index: Unwrapped ring buffer index from where iteration shall start |
| + * |
| + * Return: 0 if successful, negative error code otherwise |
| + */ |
| +static int trusty_log_start(struct trusty_log_state *s, |
| + struct trusty_log_sink_state *sink, |
| + u32 index) |
| +{ |
| + struct log_rb *log; |
| + |
| + if (WARN_ON(!s)) |
| + return -EINVAL; |
| + |
| + log = s->log; |
| + if (WARN_ON(!is_power_of_2(log->sz))) |
| + return -EINVAL; |
| + |
| + sink->get = index; |
| + return 0; |
| +} |
| + |
| +/** |
| + * trusty_log_show() - sink log entry at current iteration |
| + * @s: Current log state. |
| + * @sink: trusty_log_sink_state holding the get index on a given sink |
| + */ |
| +static void trusty_log_show(struct trusty_log_state *s, |
| + struct trusty_log_sink_state *sink) |
| +{ |
| + struct log_rb *log = s->log; |
| + u32 alloc, put, get; |
| + int read_chars; |
| + |
| + /* |
| + * For this ring buffer, at any given point, alloc >= put >= get. |
| + * The producer side of the buffer is not locked, so the put and alloc |
| + * pointers must be read in a defined order (put before alloc) so |
| + * that the above condition is maintained. A read barrier is needed |
| + * to make sure the hardware and compiler keep the reads ordered. |
| + */ |
| + get = sink->get; |
| + put = log->put; |
| + |
| + /* Make sure that the read of put occurs before the read of log data */ |
| + rmb(); |
| + |
| + /* Read a line from the log */ |
| + read_chars = log_read_line(s, put, get); |
| + |
| + /* Force the loads from log_read_line to complete. */ |
| + rmb(); |
| + alloc = log->alloc; |
| + |
| + /* |
| + * Discard the line that was just read if the data could |
| + * have been corrupted by the producer. |
| + */ |
| + if (u32_sub_overflow(alloc, get) > log->sz) { |
| + /* |
| + * this condition is acceptable in the case of the sfile sink |
| + * when attempting to read the oldest entry (at alloc-log->sz) |
| + * which may be overrun by a new one when ring buffer write |
| + * index wraps around. |
| + * So the overrun is not reported in case the oldest line |
| + * was being read. |
| + */ |
| + if (sink->sfile) { |
| + if (!sink->ignore_overflow) |
| + seq_puts(sink->sfile, "log overflow.\n"); |
| + /* coalesce subsequent contiguous overflows. */ |
| + sink->ignore_overflow = true; |
| + } else { |
| + dev_err(s->dev, "log overflow.\n"); |
| + } |
| + sink->get = u32_sub_overflow(alloc, log->sz); |
| + return; |
| + } |
| + /* compute next line index */ |
| + sink->get = u32_add_overflow(get, read_chars); |
| + /* once a line is valid, ignore_overflow must be disabled */ |
| + sink->ignore_overflow = false; |
| + if (sink->sfile) { |
| + seq_printf(sink->sfile, "%s", s->line_buffer); |
| + } else { |
| + if (sink->trusty_panicked || |
| + __ratelimit(&trusty_log_rate_limit)) { |
| + dev_info(s->dev, "%s", s->line_buffer); |
| + } |
| + } |
| +} |
| + |
| +static void *trusty_log_seq_start(struct seq_file *sfile, loff_t *pos) |
| +{ |
| + struct trusty_log_sfile *lb; |
| + struct trusty_log_state *s; |
| + struct log_rb *log; |
| + struct trusty_log_sink_state *log_sfile_sink; |
| + u32 index; |
| + int rc; |
| + |
| + if (WARN_ON(!pos)) |
| + return ERR_PTR(-EINVAL); |
| + |
| + lb = sfile->private; |
| + if (WARN_ON(!lb)) |
| + return ERR_PTR(-EINVAL); |
| + |
| + log_sfile_sink = kzalloc(sizeof(*log_sfile_sink), GFP_KERNEL); |
| + if (!log_sfile_sink) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + s = container_of(lb, struct trusty_log_state, log_sfile); |
| + log_sfile_sink->sfile = sfile; |
| + log = s->log; |
| + if (*pos == 0) { |
| + /* start at the oldest line */ |
| + index = 0; |
| + if (log->alloc > log->sz) |
| + index = u32_sub_overflow(log->alloc, log->sz); |
| + } else { |
| + /* |
| + * '*pos>0': pos hold the 32bits unwrapped index from where |
| + * to start iterating |
| + */ |
| + index = (u32)*pos; |
| + } |
| + pr_debug("%s start=%u\n", __func__, index); |
| + |
| + log_sfile_sink->ignore_overflow = true; |
| + rc = trusty_log_start(s, log_sfile_sink, index); |
| + if (rc < 0) |
| + goto free_sink; |
| + |
| + if (!trusty_log_has_data(s, log_sfile_sink)) |
| + goto free_sink; |
| + |
| + return log_sfile_sink; |
| + |
| +free_sink: |
| + pr_debug("%s kfree\n", __func__); |
| + kfree(log_sfile_sink); |
| + return rc < 0 ? ERR_PTR(rc) : NULL; |
| +} |
| + |
| +static void *trusty_log_seq_next(struct seq_file *sfile, void *v, loff_t *pos) |
| +{ |
| + struct trusty_log_sfile *lb; |
| + struct trusty_log_state *s; |
| + struct trusty_log_sink_state *log_sfile_sink = v; |
| + int rc = 0; |
| + |
| + if (WARN_ON(!log_sfile_sink)) |
| + return ERR_PTR(-EINVAL); |
| + |
| + lb = sfile->private; |
| + if (WARN_ON(!lb)) { |
| + rc = -EINVAL; |
| + goto end_of_iter; |
| + } |
| + s = container_of(lb, struct trusty_log_state, log_sfile); |
| + |
| + if (WARN_ON(!pos)) { |
| + rc = -EINVAL; |
| + goto end_of_iter; |
| + } |
| + /* |
| + * When starting a virtual file sink, the start function is invoked |
| + * with a pos argument which value is set to zero. |
| + * Subsequent starts are invoked with pos being set to |
| + * the unwrapped read index (get). |
| + * Upon u32 wraparound, the get index could be reset to zero. |
| + * Thus a msb is used to distinguish the `get` zero value |
| + * from the `start of file` zero value. |
| + */ |
| + *pos = (1UL << 32) + log_sfile_sink->get; |
| + if (!trusty_log_has_data(s, log_sfile_sink)) |
| + goto end_of_iter; |
| + |
| + return log_sfile_sink; |
| + |
| +end_of_iter: |
| + pr_debug("%s kfree\n", __func__); |
| + kfree(log_sfile_sink); |
| + return rc < 0 ? ERR_PTR(rc) : NULL; |
| +} |
| + |
| +static void trusty_log_seq_stop(struct seq_file *sfile, void *v) |
| +{ |
| + /* |
| + * When iteration completes or on error, the next callback frees |
| + * the sink structure and returns NULL/error-code. |
| + * In that case stop (being invoked with void* v set to the last next |
| + * return value) would be invoked with v == NULL or error code. |
| + * When user space stops the iteration earlier than the end |
| + * (in case of user-space memory allocation limit for example) |
| + * then the stop function receives a non NULL get pointer |
| + * and is in charge or freeing the sink structure. |
| + */ |
| + struct trusty_log_sink_state *log_sfile_sink = v; |
| + |
| + /* nothing to do - sink structure already freed */ |
| + if (IS_ERR_OR_NULL(log_sfile_sink)) |
| + return; |
| + |
| + kfree(log_sfile_sink); |
| + |
| + pr_debug("%s kfree\n", __func__); |
| +} |
| + |
| +static int trusty_log_seq_show(struct seq_file *sfile, void *v) |
| +{ |
| + struct trusty_log_sfile *lb; |
| + struct trusty_log_state *s; |
| + struct trusty_log_sink_state *log_sfile_sink = v; |
| + |
| + if (WARN_ON(!log_sfile_sink)) |
| + return -EINVAL; |
| + |
| + lb = sfile->private; |
| + if (WARN_ON(!lb)) |
| + return -EINVAL; |
| + |
| + s = container_of(lb, struct trusty_log_state, log_sfile); |
| + |
| + trusty_log_show(s, log_sfile_sink); |
| + return 0; |
| +} |
| + |
| +static void trusty_dump_logs(struct trusty_log_state *s) |
| +{ |
| + int rc; |
| + /* |
| + * note: klog_sink.get initialized to zero by kzalloc |
| + */ |
| + s->klog_sink.trusty_panicked = trusty_get_panic_status(s->trusty_dev); |
| + |
| + rc = trusty_log_start(s, &s->klog_sink, s->klog_sink.get); |
| + if (rc < 0) |
| + return; |
| + |
| + while (trusty_log_has_data(s, &s->klog_sink)) |
| + trusty_log_show(s, &s->klog_sink); |
| +} |
| + |
| +static int trusty_log_call_notify(struct notifier_block *nb, |
| + unsigned long action, void *data) |
| +{ |
| + struct trusty_log_state *s; |
| + unsigned long flags; |
| + u32 cur_put; |
| + |
| + if (action != TRUSTY_CALL_RETURNED) |
| + return NOTIFY_DONE; |
| + |
| + s = container_of(nb, struct trusty_log_state, call_notifier); |
| + spin_lock_irqsave(&s->wake_up_lock, flags); |
| + cur_put = s->log->put; |
| + if (cur_put != s->last_wake_put) { |
| + s->last_wake_put = cur_put; |
| + wake_up_all(&s->poll_waiters); |
| + } |
| + spin_unlock_irqrestore(&s->wake_up_lock, flags); |
| + return NOTIFY_OK; |
| +} |
| + |
| +static int trusty_log_panic_notify(struct notifier_block *nb, |
| + unsigned long action, void *data) |
| +{ |
| + struct trusty_log_state *s; |
| + |
| + /* |
| + * Don't grab the spin lock to hold up the panic notifier, even |
| + * though this is racy. |
| + */ |
| + s = container_of(nb, struct trusty_log_state, panic_notifier); |
| + dev_info(s->dev, "panic notifier - trusty version %s", |
| + trusty_version_str_get(s->trusty_dev)); |
| + trusty_dump_logs(s); |
| + return NOTIFY_OK; |
| +} |
| + |
| +const struct seq_operations trusty_log_seq_ops = { |
| + .start = trusty_log_seq_start, |
| + .stop = trusty_log_seq_stop, |
| + .next = trusty_log_seq_next, |
| + .show = trusty_log_seq_show, |
| +}; |
| + |
| +static int trusty_log_sfile_dev_open(struct inode *inode, struct file *file) |
| +{ |
| + struct trusty_log_sfile *ls; |
| + struct seq_file *sfile; |
| + int rc; |
| + |
| + /* |
| + * file->private_data contains a pointer to the misc_device struct |
| + * passed to misc_register() |
| + */ |
| + if (WARN_ON(!file->private_data)) |
| + return -EINVAL; |
| + |
| + ls = container_of(file->private_data, struct trusty_log_sfile, misc); |
| + |
| + /* |
| + * seq_open uses file->private_data to store the seq_file associated |
| + * with the struct file, but it must be NULL when seq_open is called |
| + */ |
| + file->private_data = NULL; |
| + rc = seq_open(file, &trusty_log_seq_ops); |
| + if (rc < 0) |
| + return rc; |
| + |
| + sfile = file->private_data; |
| + if (WARN_ON(!sfile)) |
| + return -EINVAL; |
| + |
| + sfile->private = ls; |
| + return 0; |
| +} |
| + |
| +static unsigned int trusty_log_sfile_dev_poll(struct file *filp, |
| + struct poll_table_struct *wait) |
| +{ |
| + struct seq_file *sfile; |
| + struct trusty_log_sfile *lb; |
| + struct trusty_log_state *s; |
| + struct log_rb *log; |
| + |
| + /* |
| + * trusty_log_sfile_dev_open() pointed filp->private_data to a |
| + * seq_file, and that seq_file->private to the trusty_log_sfile |
| + * field of a trusty_log_state |
| + */ |
| + sfile = filp->private_data; |
| + lb = sfile->private; |
| + s = container_of(lb, struct trusty_log_state, log_sfile); |
| + poll_wait(filp, &s->poll_waiters, wait); |
| + log = s->log; |
| + |
| + /* |
| + * Userspace has read up to filp->f_pos so far. Update klog_sink |
| + * to indicate that, so that we don't end up dumping the entire |
| + * Trusty log in case of panic. |
| + */ |
| + s->klog_sink.get = (u32)filp->f_pos; |
| + |
| + if (log->put != (u32)filp->f_pos) { |
| + /* data ready to read */ |
| + return EPOLLIN | EPOLLRDNORM; |
| + } |
| + /* no data available, go to sleep */ |
| + return 0; |
| +} |
| + |
| +static const struct file_operations log_sfile_dev_operations = { |
| + .owner = THIS_MODULE, |
| + .open = trusty_log_sfile_dev_open, |
| + .poll = trusty_log_sfile_dev_poll, |
| + .read = seq_read, |
| + .release = seq_release, |
| +}; |
| + |
| +static int trusty_log_sfile_register(struct trusty_log_state *s) |
| +{ |
| + int ret; |
| + struct trusty_log_sfile *ls = &s->log_sfile; |
| + |
| + if (WARN_ON(!ls)) |
| + return -EINVAL; |
| + |
| + snprintf(ls->device_name, sizeof(ls->device_name), |
| + "trusty-log%d", s->dev->id); |
| + ls->misc.minor = MISC_DYNAMIC_MINOR; |
| + ls->misc.name = ls->device_name; |
| + ls->misc.fops = &log_sfile_dev_operations; |
| + |
| + ret = misc_register(&ls->misc); |
| + if (ret) { |
| + dev_err(s->dev, |
| + "log_sfile error while doing misc_register ret=%d\n", |
| + ret); |
| + return ret; |
| + } |
| + dev_info(s->dev, "/dev/%s registered\n", |
| + ls->device_name); |
| + return 0; |
| +} |
| + |
| +static void trusty_log_sfile_unregister(struct trusty_log_state *s) |
| +{ |
| + struct trusty_log_sfile *ls = &s->log_sfile; |
| + |
| + misc_deregister(&ls->misc); |
| + if (s->dev) { |
| + dev_info(s->dev, "/dev/%s unregistered\n", |
| + ls->misc.name); |
| + } |
| +} |
| + |
| +static bool trusty_supports_logging(struct device *device) |
| +{ |
| + int result; |
| + |
| + result = trusty_std_call32(device, SMC_SC_SHARED_LOG_VERSION, |
| + TRUSTY_LOG_API_VERSION, 0, 0); |
| + if (result == SM_ERR_UNDEFINED_SMC) { |
| + dev_info(device, "trusty-log not supported on secure side.\n"); |
| + return false; |
| + } else if (result < 0) { |
| + dev_err(device, |
| + "trusty std call (SMC_SC_SHARED_LOG_VERSION) failed: %d\n", |
| + result); |
| + return false; |
| + } |
| + |
| + if (result != TRUSTY_LOG_API_VERSION) { |
| + dev_info(device, "unsupported api version: %d, supported: %d\n", |
| + result, TRUSTY_LOG_API_VERSION); |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +static int trusty_log_init(struct platform_device *pdev) |
| +{ |
| + struct trusty_log_state *s; |
| + struct scatterlist *sg; |
| + unsigned char *mem; |
| + int i; |
| + int result; |
| + trusty_shared_mem_id_t mem_id; |
| + int log_size; |
| + |
| + s = kzalloc(sizeof(*s), GFP_KERNEL); |
| + if (!s) { |
| + result = -ENOMEM; |
| + goto error_alloc_state; |
| + } |
| + |
| + s->dev = &pdev->dev; |
| + s->trusty_dev = s->dev->parent; |
| + |
| + s->log_num_pages = DIV_ROUND_UP(log_size_param + sizeof(struct log_rb), |
| + PAGE_SIZE); |
| + s->sg = kcalloc(s->log_num_pages, sizeof(*s->sg), GFP_KERNEL); |
| + if (!s->sg) { |
| + result = -ENOMEM; |
| + goto error_alloc_sg; |
| + } |
| + |
| + log_size = s->log_num_pages * PAGE_SIZE; |
| + mem = vzalloc(log_size); |
| + if (!mem) { |
| + result = -ENOMEM; |
| + goto error_alloc_log; |
| + } |
| + |
| + s->log = (struct log_rb *)mem; |
| + |
| + sg_init_table(s->sg, s->log_num_pages); |
| + for_each_sg(s->sg, sg, s->log_num_pages, i) { |
| + struct page *pg = vmalloc_to_page(mem + (i * PAGE_SIZE)); |
| + |
| + if (!pg) { |
| + result = -ENOMEM; |
| + goto err_share_memory; |
| + } |
| + sg_set_page(sg, pg, PAGE_SIZE, 0); |
| + } |
| + /* |
| + * This will fail for Trusty api version < TRUSTY_API_VERSION_MEM_OBJ |
| + * if s->log_num_pages > 1 |
| + * Use trusty_share_memory_compat instead of trusty_share_memory in case |
| + * s->log_num_pages == 1 and api version < TRUSTY_API_VERSION_MEM_OBJ, |
| + * In that case SMC_SC_SHARED_LOG_ADD expects a different value than |
| + * what trusty_share_memory returns |
| + */ |
| + result = trusty_share_memory_compat(s->trusty_dev, &mem_id, s->sg, |
| + s->log_num_pages, PAGE_KERNEL); |
| + if (result) { |
| + dev_err(s->dev, "trusty_share_memory failed: %d\n", result); |
| + goto err_share_memory; |
| + } |
| + s->log_pages_shared_mem_id = mem_id; |
| + |
| + result = trusty_std_call32(s->trusty_dev, |
| + SMC_SC_SHARED_LOG_ADD, |
| + (u32)(mem_id), (u32)(mem_id >> 32), |
| + log_size); |
| + if (result < 0) { |
| + dev_err(s->dev, |
| + "trusty std call (SMC_SC_SHARED_LOG_ADD) failed: %d 0x%llx\n", |
| + result, mem_id); |
| + goto error_std_call; |
| + } |
| + |
| + init_waitqueue_head(&s->poll_waiters); |
| + spin_lock_init(&s->wake_up_lock); |
| + |
| + s->call_notifier.notifier_call = trusty_log_call_notify; |
| + result = trusty_call_notifier_register(s->trusty_dev, |
| + &s->call_notifier); |
| + if (result < 0) { |
| + dev_err(&pdev->dev, |
| + "failed to register trusty call notifier\n"); |
| + goto error_call_notifier; |
| + } |
| + |
| + s->panic_notifier.notifier_call = trusty_log_panic_notify; |
| + result = atomic_notifier_chain_register(&panic_notifier_list, |
| + &s->panic_notifier); |
| + if (result < 0) { |
| + dev_err(&pdev->dev, |
| + "failed to register panic notifier\n"); |
| + goto error_panic_notifier; |
| + } |
| + |
| + result = trusty_log_sfile_register(s); |
| + if (result < 0) { |
| + dev_err(&pdev->dev, "failed to register log_sfile\n"); |
| + goto error_log_sfile; |
| + } |
| + |
| + platform_set_drvdata(pdev, s); |
| + |
| + return 0; |
| + |
| +error_log_sfile: |
| + atomic_notifier_chain_unregister(&panic_notifier_list, |
| + &s->panic_notifier); |
| +error_panic_notifier: |
| + trusty_call_notifier_unregister(s->trusty_dev, &s->call_notifier); |
| +error_call_notifier: |
| + trusty_std_call32(s->trusty_dev, SMC_SC_SHARED_LOG_RM, |
| + (u32)mem_id, (u32)(mem_id >> 32), 0); |
| +error_std_call: |
| + if (WARN_ON(trusty_reclaim_memory(s->trusty_dev, mem_id, s->sg, |
| + s->log_num_pages))) { |
| + dev_err(&pdev->dev, "trusty_revoke_memory failed: %d 0x%llx\n", |
| + result, mem_id); |
| + /* |
| + * It is not safe to free this memory if trusty_revoke_memory |
| + * fails. Leak it in that case. |
| + */ |
| + } else { |
| +err_share_memory: |
| + vfree(s->log); |
| + } |
| +error_alloc_log: |
| + kfree(s->sg); |
| +error_alloc_sg: |
| + kfree(s); |
| +error_alloc_state: |
| + return result; |
| +} |
| + |
| +static int trusty_log_probe(struct platform_device *pdev) |
| +{ |
| + int rc; |
| + |
| + if (!trusty_supports_logging(pdev->dev.parent)) |
| + return -ENXIO; |
| + |
| + rc = trusty_log_init(pdev); |
| + if (rc && log_size_param > TRUSTY_LOG_MIN_SIZE) { |
| + dev_warn(&pdev->dev, "init failed, retrying with 1-page log\n"); |
| + log_size_param = TRUSTY_LOG_MIN_SIZE; |
| + rc = trusty_log_init(pdev); |
| + } |
| + return rc; |
| +} |
| + |
| +static int trusty_log_remove(struct platform_device *pdev) |
| +{ |
| + int result; |
| + struct trusty_log_state *s = platform_get_drvdata(pdev); |
| + trusty_shared_mem_id_t mem_id = s->log_pages_shared_mem_id; |
| + |
| + trusty_log_sfile_unregister(s); |
| + atomic_notifier_chain_unregister(&panic_notifier_list, |
| + &s->panic_notifier); |
| + trusty_call_notifier_unregister(s->trusty_dev, &s->call_notifier); |
| + |
| + result = trusty_std_call32(s->trusty_dev, SMC_SC_SHARED_LOG_RM, |
| + (u32)mem_id, (u32)(mem_id >> 32), 0); |
| + if (result) { |
| + dev_err(&pdev->dev, |
| + "trusty std call (SMC_SC_SHARED_LOG_RM) failed: %d\n", |
| + result); |
| + } |
| + result = trusty_reclaim_memory(s->trusty_dev, mem_id, s->sg, |
| + s->log_num_pages); |
| + if (WARN_ON(result)) { |
| + dev_err(&pdev->dev, |
| + "trusty failed to remove shared memory: %d\n", result); |
| + } else { |
| + /* |
| + * It is not safe to free this memory if trusty_revoke_memory |
| + * fails. Leak it in that case. |
| + */ |
| + vfree(s->log); |
| + } |
| + kfree(s->sg); |
| + kfree(s); |
| + |
| + return 0; |
| +} |
| + |
| +static const struct of_device_id trusty_test_of_match[] = { |
| + { .compatible = "android,trusty-log-v1", }, |
| + {}, |
| +}; |
| + |
| +MODULE_DEVICE_TABLE(trusty, trusty_test_of_match); |
| + |
| +static struct platform_driver trusty_log_driver = { |
| + .probe = trusty_log_probe, |
| + .remove = trusty_log_remove, |
| + .driver = { |
| + .name = "trusty-log", |
| + .of_match_table = trusty_test_of_match, |
| + }, |
| +}; |
| + |
| +module_platform_driver(trusty_log_driver); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_DESCRIPTION("Trusty logging driver"); |
| diff --git a/drivers/trusty/trusty-log.h b/drivers/trusty/trusty-log.h |
| new file mode 100644 |
| index 000000000000..7b5e6096b51e |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-log.h |
| @@ -0,0 +1,28 @@ |
| +/* SPDX-License-Identifier: MIT */ |
| +/* |
| + * Copyright (c) 2015 Google, Inc. |
| + * |
| + * Trusty also has a copy of this header. Please keep the copies in sync. |
| + */ |
| +#ifndef _TRUSTY_LOG_H_ |
| +#define _TRUSTY_LOG_H_ |
| + |
| +/* |
| + * Ring buffer that supports one secure producer thread and one |
| + * linux side consumer thread. |
| + */ |
| +struct log_rb { |
| + volatile uint32_t alloc; |
| + volatile uint32_t put; |
| + uint32_t sz; |
| + volatile char data[]; |
| +} __packed; |
| + |
| +#define SMC_SC_SHARED_LOG_VERSION SMC_STDCALL_NR(SMC_ENTITY_LOGGING, 0) |
| +#define SMC_SC_SHARED_LOG_ADD SMC_STDCALL_NR(SMC_ENTITY_LOGGING, 1) |
| +#define SMC_SC_SHARED_LOG_RM SMC_STDCALL_NR(SMC_ENTITY_LOGGING, 2) |
| + |
| +#define TRUSTY_LOG_API_VERSION 1 |
| + |
| +#endif |
| + |
| diff --git a/drivers/trusty/trusty-mem.c b/drivers/trusty/trusty-mem.c |
| new file mode 100644 |
| index 000000000000..8a360298e501 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-mem.c |
| @@ -0,0 +1,139 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2015 Google, Inc. |
| + */ |
| + |
| +#include <linux/types.h> |
| +#include <linux/printk.h> |
| +#include <linux/trusty/arm_ffa.h> |
| +#include <linux/trusty/trusty.h> |
| +#include <linux/trusty/smcall.h> |
| + |
| +#define MEM_ATTR_STRONGLY_ORDERED (0x00U) |
| +#define MEM_ATTR_DEVICE (0x04U) |
| +#define MEM_ATTR_NORMAL_NON_CACHEABLE (0x44U) |
| +#define MEM_ATTR_NORMAL_WRITE_THROUGH (0xAAU) |
| +#define MEM_ATTR_NORMAL_WRITE_BACK_READ_ALLOCATE (0xEEU) |
| +#define MEM_ATTR_NORMAL_WRITE_BACK_WRITE_ALLOCATE (0xFFU) |
| + |
| +#define ATTR_RDONLY (1U << 7) |
| +#define ATTR_INNER_SHAREABLE (3U << 8) |
| + |
| +static int get_mem_attr(struct page *page, pgprot_t pgprot) |
| +{ |
| +#if defined(CONFIG_ARM64) |
| + u64 mair; |
| + unsigned int attr_index = (pgprot_val(pgprot) & PTE_ATTRINDX_MASK) >> 2; |
| + |
| + asm ("mrs %0, mair_el1\n" : "=&r" (mair)); |
| + return (mair >> (attr_index * 8)) & 0xff; |
| + |
| +#elif defined(CONFIG_ARM_LPAE) |
| + u32 mair; |
| + unsigned int attr_index = ((pgprot_val(pgprot) & L_PTE_MT_MASK) >> 2); |
| + |
| + if (attr_index >= 4) { |
| + attr_index -= 4; |
| + asm volatile("mrc p15, 0, %0, c10, c2, 1\n" : "=&r" (mair)); |
| + } else { |
| + asm volatile("mrc p15, 0, %0, c10, c2, 0\n" : "=&r" (mair)); |
| + } |
| + return (mair >> (attr_index * 8)) & 0xff; |
| + |
| +#elif defined(CONFIG_ARM) |
| + /* check memory type */ |
| + switch (pgprot_val(pgprot) & L_PTE_MT_MASK) { |
| + case L_PTE_MT_WRITEALLOC: |
| + return MEM_ATTR_NORMAL_WRITE_BACK_WRITE_ALLOCATE; |
| + |
| + case L_PTE_MT_BUFFERABLE: |
| + return MEM_ATTR_NORMAL_NON_CACHEABLE; |
| + |
| + case L_PTE_MT_WRITEBACK: |
| + return MEM_ATTR_NORMAL_WRITE_BACK_READ_ALLOCATE; |
| + |
| + case L_PTE_MT_WRITETHROUGH: |
| + return MEM_ATTR_NORMAL_WRITE_THROUGH; |
| + |
| + case L_PTE_MT_UNCACHED: |
| + return MEM_ATTR_STRONGLY_ORDERED; |
| + |
| + case L_PTE_MT_DEV_SHARED: |
| + case L_PTE_MT_DEV_NONSHARED: |
| + return MEM_ATTR_DEVICE; |
| + |
| + default: |
| + return -EINVAL; |
| + } |
| +#else |
| + return 0; |
| +#endif |
| +} |
| + |
| +int trusty_encode_page_info(struct ns_mem_page_info *inf, |
| + struct page *page, pgprot_t pgprot) |
| +{ |
| + int mem_attr; |
| + u64 pte; |
| + u8 ffa_mem_attr; |
| + u8 ffa_mem_perm = 0; |
| + |
| + if (!inf || !page) |
| + return -EINVAL; |
| + |
| + /* get physical address */ |
| + pte = (u64)page_to_phys(page); |
| + |
| + /* get memory attributes */ |
| + mem_attr = get_mem_attr(page, pgprot); |
| + if (mem_attr < 0) |
| + return mem_attr; |
| + |
| + switch (mem_attr) { |
| + case MEM_ATTR_STRONGLY_ORDERED: |
| + ffa_mem_attr = FFA_MEM_ATTR_DEVICE_NGNRNE; |
| + break; |
| + |
| + case MEM_ATTR_DEVICE: |
| + ffa_mem_attr = FFA_MEM_ATTR_DEVICE_NGNRE; |
| + break; |
| + |
| + case MEM_ATTR_NORMAL_NON_CACHEABLE: |
| + ffa_mem_attr = FFA_MEM_ATTR_NORMAL_MEMORY_UNCACHED; |
| + break; |
| + |
| + case MEM_ATTR_NORMAL_WRITE_BACK_READ_ALLOCATE: |
| + case MEM_ATTR_NORMAL_WRITE_BACK_WRITE_ALLOCATE: |
| + ffa_mem_attr = FFA_MEM_ATTR_NORMAL_MEMORY_CACHED_WB; |
| + break; |
| + |
| + default: |
| + return -EINVAL; |
| + } |
| + |
| + inf->paddr = pte; |
| + |
| + /* add other attributes */ |
| +#if defined(CONFIG_ARM64) || defined(CONFIG_ARM_LPAE) |
| + pte |= pgprot_val(pgprot); |
| +#elif defined(CONFIG_ARM) |
| + if (pgprot_val(pgprot) & L_PTE_RDONLY) |
| + pte |= ATTR_RDONLY; |
| + if (pgprot_val(pgprot) & L_PTE_SHARED) |
| + pte |= ATTR_INNER_SHAREABLE; /* inner sharable */ |
| +#endif |
| + |
| + if (!(pte & ATTR_RDONLY)) |
| + ffa_mem_perm |= FFA_MEM_PERM_RW; |
| + else |
| + ffa_mem_perm |= FFA_MEM_PERM_RO; |
| + |
| + if ((pte & ATTR_INNER_SHAREABLE) == ATTR_INNER_SHAREABLE) |
| + ffa_mem_attr |= FFA_MEM_ATTR_INNER_SHAREABLE; |
| + |
| + inf->ffa_mem_attr = ffa_mem_attr; |
| + inf->ffa_mem_perm = ffa_mem_perm; |
| + inf->compat_attr = (pte & 0x0000FFFFFFFFFFFFull) | |
| + ((u64)mem_attr << 48); |
| + return 0; |
| +} |
| diff --git a/drivers/trusty/trusty-smc-arm.S b/drivers/trusty/trusty-smc-arm.S |
| new file mode 100644 |
| index 000000000000..8ff83547d33f |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-smc-arm.S |
| @@ -0,0 +1,41 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (C) 2020 Google, Inc. |
| + */ |
| + |
| +#include <linux/linkage.h> |
| + |
| +.arch_extension sec |
| + |
| +ENTRY(trusty_smc8) |
| + /* Save stack location where r3-r7 smc arguments are stored */ |
| + mov r12, sp |
| + |
| + /* Save original r4-r7 values as caller expects these to be preserved */ |
| + push {r4-r7} |
| + |
| + /* Save return value pointer and return address */ |
| + push {r0, lr} |
| + |
| + /* arm abi shifts arguments when returning a struct, shift them back */ |
| + mov r0, r1 |
| + mov r1, r2 |
| + mov r2, r3 |
| + |
| + /* Load stack based arguments */ |
| + ldmia r12, {r3-r7} |
| + |
| + smc #0 |
| + |
| + /* Restore return address and get return value pointer */ |
| + pop {r12, lr} |
| + |
| + /* Copy 8-register smc return value to struct smc_ret8 return value */ |
| + stmia r12, {r0-r7} |
| + |
| + /* Restore original r4-r7 values */ |
| + pop {r4-r7} |
| + |
| + /* Return */ |
| + bx lr |
| +ENDPROC(trusty_smc8) |
| diff --git a/drivers/trusty/trusty-smc-arm64.S b/drivers/trusty/trusty-smc-arm64.S |
| new file mode 100644 |
| index 000000000000..14c8fed28a5e |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-smc-arm64.S |
| @@ -0,0 +1,35 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (C) 2020 Google, Inc. |
| + */ |
| + |
| +#include <linux/linkage.h> |
| + |
| +.macro push ra, rb |
| +stp \ra, \rb, [sp,#-16]! |
| +.endm |
| + |
| +.macro pop ra, rb |
| +ldp \ra, \rb, [sp], #16 |
| +.endm |
| + |
| +lr .req x30 |
| + |
| +SYM_FUNC_START(trusty_smc8) |
| + /* |
| + * Save x8 (return value ptr) and lr. The SMC calling convention says el3 |
| + * does not need to preserve x8. The normal ABI does not require either x8 |
| + * or lr to be preserved. |
| + */ |
| + push x8, lr |
| + smc #0 |
| + pop x8, lr |
| + |
| + /* Copy 8-register smc return value to struct smc_ret8 return value */ |
| + stp x0, x1, [x8], #16 |
| + stp x2, x3, [x8], #16 |
| + stp x4, x5, [x8], #16 |
| + stp x6, x7, [x8], #16 |
| + |
| + ret |
| +SYM_FUNC_END(trusty_smc8) |
| diff --git a/drivers/trusty/trusty-smc.h b/drivers/trusty/trusty-smc.h |
| new file mode 100644 |
| index 000000000000..b53e5abb4d05 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-smc.h |
| @@ -0,0 +1,26 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (C) 2020 Google, Inc. |
| + */ |
| +#ifndef _TRUSTY_SMC_H |
| +#define _TRUSTY_SMC_H |
| + |
| +#include <linux/types.h> |
| + |
| +struct smc_ret8 { |
| + unsigned long r0; |
| + unsigned long r1; |
| + unsigned long r2; |
| + unsigned long r3; |
| + unsigned long r4; |
| + unsigned long r5; |
| + unsigned long r6; |
| + unsigned long r7; |
| +}; |
| + |
| +struct smc_ret8 trusty_smc8(unsigned long r0, unsigned long r1, |
| + unsigned long r2, unsigned long r3, |
| + unsigned long r4, unsigned long r5, |
| + unsigned long r6, unsigned long r7); |
| + |
| +#endif /* _TRUSTY_SMC_H */ |
| diff --git a/drivers/trusty/trusty-test.c b/drivers/trusty/trusty-test.c |
| new file mode 100644 |
| index 000000000000..844868981fa5 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-test.c |
| @@ -0,0 +1,440 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2020 Google, Inc. |
| + */ |
| + |
| +#include <linux/ctype.h> |
| +#include <linux/list.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/trusty/smcall.h> |
| +#include <linux/trusty/trusty.h> |
| +#include <linux/scatterlist.h> |
| +#include <linux/slab.h> |
| +#include <linux/mm.h> |
| +#include <linux/mod_devicetable.h> |
| +#include <linux/module.h> |
| + |
| +#include "trusty-test.h" |
| + |
| +struct trusty_test_state { |
| + struct device *dev; |
| + struct device *trusty_dev; |
| +}; |
| + |
| +struct trusty_test_shmem_obj { |
| + struct list_head node; |
| + size_t page_count; |
| + struct page **pages; |
| + void *buf; |
| + struct sg_table sgt; |
| + trusty_shared_mem_id_t mem_id; |
| +}; |
| + |
| +/* |
| + * Allocate a test object with @page_count number of pages, map it and add it to |
| + * @list. |
| + * For multi-page allocations, order the pages so they are not contiguous. |
| + */ |
| +static int trusty_test_alloc_obj(struct trusty_test_state *s, |
| + size_t page_count, |
| + struct list_head *list) |
| +{ |
| + size_t i; |
| + int ret = -ENOMEM; |
| + struct trusty_test_shmem_obj *obj; |
| + |
| + obj = kzalloc(sizeof(*obj), GFP_KERNEL); |
| + if (!obj) |
| + goto err_alloc_obj; |
| + obj->page_count = page_count; |
| + |
| + obj->pages = kmalloc_array(page_count, sizeof(*obj->pages), GFP_KERNEL); |
| + if (!obj->pages) { |
| + ret = -ENOMEM; |
| + dev_err(s->dev, "failed to allocate page array, count %zd\n", |
| + page_count); |
| + goto err_alloc_pages; |
| + } |
| + |
| + for (i = 0; i < page_count; i++) { |
| + obj->pages[i] = alloc_page(GFP_KERNEL); |
| + if (!obj->pages[i]) { |
| + ret = -ENOMEM; |
| + dev_err(s->dev, "failed to allocate page %zd/%zd\n", |
| + i, page_count); |
| + goto err_alloc_page; |
| + } |
| + if (i > 0 && obj->pages[i - 1] + 1 == obj->pages[i]) { |
| + /* swap adacent pages to increase fragmentation */ |
| + swap(obj->pages[i - 1], obj->pages[i]); |
| + } |
| + } |
| + |
| + obj->buf = vmap(obj->pages, page_count, VM_MAP, PAGE_KERNEL); |
| + if (!obj->buf) { |
| + ret = -ENOMEM; |
| + dev_err(s->dev, "failed to map test buffer page count %zd\n", |
| + page_count); |
| + goto err_map_pages; |
| + } |
| + |
| + ret = sg_alloc_table_from_pages(&obj->sgt, obj->pages, page_count, |
| + 0, page_count * PAGE_SIZE, GFP_KERNEL); |
| + if (ret) { |
| + dev_err(s->dev, "sg_alloc_table_from_pages failed: %d\n", ret); |
| + goto err_alloc_sgt; |
| + } |
| + list_add_tail(&obj->node, list); |
| + dev_dbg(s->dev, "buffer has %d page runs\n", obj->sgt.nents); |
| + return 0; |
| + |
| +err_alloc_sgt: |
| + vunmap(obj->buf); |
| +err_map_pages: |
| + for (i = page_count; i > 0; i--) { |
| + __free_page(obj->pages[i - 1]); |
| +err_alloc_page: |
| + ; |
| + } |
| + kfree(obj->pages); |
| +err_alloc_pages: |
| + kfree(obj); |
| +err_alloc_obj: |
| + return ret; |
| +} |
| + |
| +/* Unlink, unmap and free a test object and its pages */ |
| +static void trusty_test_free_obj(struct trusty_test_state *s, |
| + struct trusty_test_shmem_obj *obj) |
| +{ |
| + size_t i; |
| + |
| + list_del(&obj->node); |
| + sg_free_table(&obj->sgt); |
| + vunmap(obj->buf); |
| + for (i = obj->page_count; i > 0; i--) |
| + __free_page(obj->pages[i - 1]); |
| + kfree(obj->pages); |
| + kfree(obj); |
| +} |
| + |
| +/* |
| + * Share all the pages of all the test object in &obj_list. |
| + * If sharing a test object fails, free it so that every test object that |
| + * remains in @obj_list has been shared when this function returns. |
| + * Return a error if any test object failed to be shared. |
| + */ |
| +static int trusty_test_share_objs(struct trusty_test_state *s, |
| + struct list_head *obj_list, size_t size) |
| +{ |
| + int ret = 0; |
| + int tmpret; |
| + struct trusty_test_shmem_obj *obj; |
| + struct trusty_test_shmem_obj *next_obj; |
| + ktime_t t1; |
| + ktime_t t2; |
| + |
| + list_for_each_entry_safe(obj, next_obj, obj_list, node) { |
| + t1 = ktime_get(); |
| + tmpret = trusty_share_memory(s->trusty_dev, &obj->mem_id, |
| + obj->sgt.sgl, obj->sgt.nents, |
| + PAGE_KERNEL); |
| + t2 = ktime_get(); |
| + if (tmpret) { |
| + ret = tmpret; |
| + dev_err(s->dev, |
| + "trusty_share_memory failed: %d, size=%zd\n", |
| + ret, size); |
| + |
| + /* |
| + * Free obj and continue, so we can revoke the |
| + * whole list in trusty_test_reclaim_objs. |
| + */ |
| + trusty_test_free_obj(s, obj); |
| + } |
| + dev_dbg(s->dev, "share id=0x%llx, size=%zu took %lld ns\n", |
| + obj->mem_id, size, |
| + ktime_to_ns(ktime_sub(t2, t1))); |
| + } |
| + |
| + return ret; |
| +} |
| + |
| +/* Reclaim memory shared with trusty for all test objects in @obj_list. */ |
| +static int trusty_test_reclaim_objs(struct trusty_test_state *s, |
| + struct list_head *obj_list, size_t size) |
| +{ |
| + int ret = 0; |
| + int tmpret; |
| + struct trusty_test_shmem_obj *obj; |
| + struct trusty_test_shmem_obj *next_obj; |
| + ktime_t t1; |
| + ktime_t t2; |
| + |
| + list_for_each_entry_safe(obj, next_obj, obj_list, node) { |
| + t1 = ktime_get(); |
| + tmpret = trusty_reclaim_memory(s->trusty_dev, obj->mem_id, |
| + obj->sgt.sgl, obj->sgt.nents); |
| + t2 = ktime_get(); |
| + if (tmpret) { |
| + ret = tmpret; |
| + dev_err(s->dev, |
| + "trusty_reclaim_memory failed: %d, id=0x%llx\n", |
| + ret, obj->mem_id); |
| + |
| + /* |
| + * It is not safe to free this memory if |
| + * trusty_reclaim_memory fails. Leak it in that |
| + * case. |
| + */ |
| + list_del(&obj->node); |
| + } |
| + dev_dbg(s->dev, "revoke id=0x%llx, size=%zu took %lld ns\n", |
| + obj->mem_id, size, |
| + ktime_to_ns(ktime_sub(t2, t1))); |
| + } |
| + |
| + return ret; |
| +} |
| + |
| +/* |
| + * Test a test object. First, initialize the memory, then make a std call into |
| + * trusty which will read it and return an error if the initialized value does |
| + * not match what it expects. If trusty reads the correct values, it will modify |
| + * the memory and return 0. This function then checks that it can read the |
| + * correct modified value. |
| + */ |
| +static int trusty_test_rw(struct trusty_test_state *s, |
| + struct trusty_test_shmem_obj *obj) |
| +{ |
| + size_t size = obj->page_count * PAGE_SIZE; |
| + int ret; |
| + size_t i; |
| + u64 *buf = obj->buf; |
| + ktime_t t1; |
| + ktime_t t2; |
| + |
| + for (i = 0; i < size / sizeof(*buf); i++) |
| + buf[i] = i; |
| + |
| + t1 = ktime_get(); |
| + ret = trusty_std_call32(s->trusty_dev, SMC_SC_TEST_SHARED_MEM_RW, |
| + (u32)(obj->mem_id), (u32)(obj->mem_id >> 32), |
| + size); |
| + t2 = ktime_get(); |
| + if (ret < 0) { |
| + dev_err(s->dev, |
| + "trusty std call (SMC_SC_TEST_SHARED_MEM_RW) failed: %d 0x%llx\n", |
| + ret, obj->mem_id); |
| + return ret; |
| + } |
| + |
| + for (i = 0; i < size / sizeof(*buf); i++) { |
| + if (buf[i] != size - i) { |
| + dev_err(s->dev, |
| + "input mismatch at %zd, got 0x%llx instead of 0x%zx\n", |
| + i, buf[i], size - i); |
| + return -EIO; |
| + } |
| + } |
| + |
| + dev_dbg(s->dev, "rw id=0x%llx, size=%zu took %lld ns\n", obj->mem_id, |
| + size, ktime_to_ns(ktime_sub(t2, t1))); |
| + |
| + return 0; |
| +} |
| + |
| +/* |
| + * Run test on every test object in @obj_list. Repeat @repeat_access times. |
| + */ |
| +static int trusty_test_rw_objs(struct trusty_test_state *s, |
| + struct list_head *obj_list, |
| + size_t repeat_access) |
| +{ |
| + int ret; |
| + size_t i; |
| + struct trusty_test_shmem_obj *obj; |
| + |
| + for (i = 0; i < repeat_access; i++) { |
| + /* |
| + * Repeat test in case the memory attributes don't match |
| + * and either side see old data. |
| + */ |
| + list_for_each_entry(obj, obj_list, node) { |
| + ret = trusty_test_rw(s, obj); |
| + if (ret) |
| + return ret; |
| + } |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +/* |
| + * Allocate @obj_count test object that each have @page_count pages. Share each |
| + * object @repeat_share times, each time running tests on every object |
| + * @repeat_access times. |
| + */ |
| +static int trusty_test_run(struct trusty_test_state *s, size_t page_count, |
| + size_t obj_count, size_t repeat_share, |
| + size_t repeat_access) |
| +{ |
| + int ret = 0; |
| + int tmpret; |
| + size_t i; |
| + size_t size = page_count * PAGE_SIZE; |
| + LIST_HEAD(obj_list); |
| + struct trusty_test_shmem_obj *obj; |
| + struct trusty_test_shmem_obj *next_obj; |
| + |
| + for (i = 0; i < obj_count && !ret; i++) |
| + ret = trusty_test_alloc_obj(s, page_count, &obj_list); |
| + |
| + for (i = 0; i < repeat_share && !ret; i++) { |
| + ret = trusty_test_share_objs(s, &obj_list, size); |
| + if (ret) { |
| + dev_err(s->dev, |
| + "trusty_share_memory failed: %d, i=%zd/%zd, size=%zd\n", |
| + ret, i, repeat_share, size); |
| + } else { |
| + ret = trusty_test_rw_objs(s, &obj_list, repeat_access); |
| + if (ret) |
| + dev_err(s->dev, |
| + "test failed: %d, i=%zd/%zd, size=%zd\n", |
| + ret, i, repeat_share, size); |
| + } |
| + tmpret = trusty_test_reclaim_objs(s, &obj_list, size); |
| + if (tmpret) { |
| + ret = tmpret; |
| + dev_err(s->dev, |
| + "trusty_reclaim_memory failed: %d, i=%zd/%zd\n", |
| + ret, i, repeat_share); |
| + } |
| + } |
| + |
| + list_for_each_entry_safe(obj, next_obj, &obj_list, node) |
| + trusty_test_free_obj(s, obj); |
| + |
| + dev_info(s->dev, "[ %s ] size %zd, obj_count %zd, repeat_share %zd, repeat_access %zd\n", |
| + ret ? "FAILED" : "PASSED", size, obj_count, repeat_share, |
| + repeat_access); |
| + |
| + return ret; |
| +} |
| + |
| +/* |
| + * Get an optional numeric argument from @buf, update @buf and return the value. |
| + * If @buf does not start with ",", return @default_val instead. |
| + */ |
| +static size_t trusty_test_get_arg(const char **buf, size_t default_val) |
| +{ |
| + char *buf_next; |
| + size_t ret; |
| + |
| + if (**buf != ',') |
| + return default_val; |
| + |
| + (*buf)++; |
| + ret = simple_strtoul(*buf, &buf_next, 0); |
| + if (buf_next == *buf) |
| + return default_val; |
| + |
| + *buf = buf_next; |
| + |
| + return ret; |
| +} |
| + |
| +/* |
| + * Run tests described by a string in this format: |
| + * <obj_size>,<obj_count=1>,<repeat_share=1>,<repeat_access=3> |
| + */ |
| +static ssize_t trusty_test_run_store(struct device *dev, |
| + struct device_attribute *attr, |
| + const char *buf, size_t count) |
| +{ |
| + struct platform_device *pdev = to_platform_device(dev); |
| + struct trusty_test_state *s = platform_get_drvdata(pdev); |
| + size_t size; |
| + size_t obj_count; |
| + size_t repeat_share; |
| + size_t repeat_access; |
| + int ret; |
| + char *buf_next; |
| + |
| + while (true) { |
| + while (isspace(*buf)) |
| + buf++; |
| + size = simple_strtoul(buf, &buf_next, 0); |
| + if (buf_next == buf) |
| + return count; |
| + buf = buf_next; |
| + obj_count = trusty_test_get_arg(&buf, 1); |
| + repeat_share = trusty_test_get_arg(&buf, 1); |
| + repeat_access = trusty_test_get_arg(&buf, 3); |
| + |
| + ret = trusty_test_run(s, DIV_ROUND_UP(size, PAGE_SIZE), |
| + obj_count, repeat_share, repeat_access); |
| + if (ret) |
| + return ret; |
| + } |
| +} |
| + |
| +static DEVICE_ATTR_WO(trusty_test_run); |
| + |
| +static struct attribute *trusty_test_attrs[] = { |
| + &dev_attr_trusty_test_run.attr, |
| + NULL, |
| +}; |
| +ATTRIBUTE_GROUPS(trusty_test); |
| + |
| +static int trusty_test_probe(struct platform_device *pdev) |
| +{ |
| + struct trusty_test_state *s; |
| + int ret; |
| + |
| + ret = trusty_std_call32(pdev->dev.parent, SMC_SC_TEST_VERSION, |
| + TRUSTY_STDCALLTEST_API_VERSION, 0, 0); |
| + if (ret != TRUSTY_STDCALLTEST_API_VERSION) |
| + return -ENOENT; |
| + |
| + s = kzalloc(sizeof(*s), GFP_KERNEL); |
| + if (!s) |
| + return -ENOMEM; |
| + |
| + s->dev = &pdev->dev; |
| + s->trusty_dev = s->dev->parent; |
| + |
| + platform_set_drvdata(pdev, s); |
| + |
| + return 0; |
| +} |
| + |
| +static int trusty_test_remove(struct platform_device *pdev) |
| +{ |
| + struct trusty_log_state *s = platform_get_drvdata(pdev); |
| + |
| + kfree(s); |
| + return 0; |
| +} |
| + |
| +static const struct of_device_id trusty_test_of_match[] = { |
| + { .compatible = "android,trusty-test-v1", }, |
| + {}, |
| +}; |
| + |
| +MODULE_DEVICE_TABLE(trusty, trusty_test_of_match); |
| + |
| +static struct platform_driver trusty_test_driver = { |
| + .probe = trusty_test_probe, |
| + .remove = trusty_test_remove, |
| + .driver = { |
| + .name = "trusty-test", |
| + .of_match_table = trusty_test_of_match, |
| + .dev_groups = trusty_test_groups, |
| + }, |
| +}; |
| + |
| +module_platform_driver(trusty_test_driver); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_DESCRIPTION("Trusty test driver"); |
| diff --git a/drivers/trusty/trusty-test.h b/drivers/trusty/trusty-test.h |
| new file mode 100644 |
| index 000000000000..eea7beb96876 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-test.h |
| @@ -0,0 +1,13 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (c) 2020 Google, Inc. |
| + */ |
| +#ifndef _TRUSTY_TEST_H |
| +#define _TRUSTY_TEST_H |
| + |
| +#define SMC_SC_TEST_VERSION SMC_STDCALL_NR(SMC_ENTITY_TEST, 0) |
| +#define SMC_SC_TEST_SHARED_MEM_RW SMC_STDCALL_NR(SMC_ENTITY_TEST, 1) |
| + |
| +#define TRUSTY_STDCALLTEST_API_VERSION 1 |
| + |
| +#endif /* _TRUSTY_TEST_H */ |
| diff --git a/drivers/trusty/trusty-virtio.c b/drivers/trusty/trusty-virtio.c |
| new file mode 100644 |
| index 000000000000..fea59cd2e218 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty-virtio.c |
| @@ -0,0 +1,840 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Trusty Virtio driver |
| + * |
| + * Copyright (C) 2015 Google, Inc. |
| + */ |
| +#include <linux/device.h> |
| +#include <linux/err.h> |
| +#include <linux/kernel.h> |
| + |
| +#include <linux/dma-map-ops.h> |
| +#include <linux/module.h> |
| +#include <linux/mutex.h> |
| +#include <linux/notifier.h> |
| +#include <linux/workqueue.h> |
| +#include <linux/remoteproc.h> |
| +#include <linux/slab.h> |
| + |
| +#include <linux/platform_device.h> |
| +#include <linux/trusty/smcall.h> |
| +#include <linux/trusty/trusty.h> |
| +#include <linux/trusty/trusty_ipc.h> |
| + |
| +#include <linux/virtio.h> |
| +#include <linux/virtio_config.h> |
| +#include <linux/virtio_ids.h> |
| +#include <linux/virtio_ring.h> |
| + |
| +#include <linux/atomic.h> |
| + |
| +#define RSC_DESCR_VER 1 |
| + |
| +struct trusty_vdev; |
| + |
| +struct trusty_ctx { |
| + struct device *dev; |
| + void *shared_va; |
| + struct scatterlist shared_sg; |
| + trusty_shared_mem_id_t shared_id; |
| + size_t shared_sz; |
| + struct work_struct check_vqs; |
| + struct work_struct kick_vqs; |
| + struct notifier_block call_notifier; |
| + struct list_head vdev_list; |
| + struct mutex mlock; /* protects vdev_list */ |
| + struct workqueue_struct *kick_wq; |
| + struct workqueue_struct *check_wq; |
| +}; |
| + |
| +struct trusty_vring { |
| + void *vaddr; |
| + struct scatterlist sg; |
| + trusty_shared_mem_id_t shared_mem_id; |
| + size_t size; |
| + unsigned int align; |
| + unsigned int elem_num; |
| + u32 notifyid; |
| + atomic_t needs_kick; |
| + struct fw_rsc_vdev_vring *vr_descr; |
| + struct virtqueue *vq; |
| + struct trusty_vdev *tvdev; |
| + struct trusty_nop kick_nop; |
| +}; |
| + |
| +struct trusty_vdev { |
| + struct list_head node; |
| + struct virtio_device vdev; |
| + struct trusty_ctx *tctx; |
| + u32 notifyid; |
| + unsigned int config_len; |
| + void *config; |
| + struct fw_rsc_vdev *vdev_descr; |
| + unsigned int vring_num; |
| + struct trusty_vring vrings[]; |
| +}; |
| + |
| +#define vdev_to_tvdev(vd) container_of((vd), struct trusty_vdev, vdev) |
| + |
| +static void check_all_vqs(struct work_struct *work) |
| +{ |
| + unsigned int i; |
| + struct trusty_ctx *tctx = container_of(work, struct trusty_ctx, |
| + check_vqs); |
| + struct trusty_vdev *tvdev; |
| + |
| + list_for_each_entry(tvdev, &tctx->vdev_list, node) { |
| + for (i = 0; i < tvdev->vring_num; i++) |
| + if (tvdev->vrings[i].vq) |
| + vring_interrupt(0, tvdev->vrings[i].vq); |
| + } |
| +} |
| + |
| +static int trusty_call_notify(struct notifier_block *nb, |
| + unsigned long action, void *data) |
| +{ |
| + struct trusty_ctx *tctx; |
| + |
| + if (action != TRUSTY_CALL_RETURNED) |
| + return NOTIFY_DONE; |
| + |
| + tctx = container_of(nb, struct trusty_ctx, call_notifier); |
| + queue_work(tctx->check_wq, &tctx->check_vqs); |
| + |
| + return NOTIFY_OK; |
| +} |
| + |
| +static void kick_vq(struct trusty_ctx *tctx, |
| + struct trusty_vdev *tvdev, |
| + struct trusty_vring *tvr) |
| +{ |
| + int ret; |
| + |
| + dev_dbg(tctx->dev, "%s: vdev_id=%d: vq_id=%d\n", |
| + __func__, tvdev->notifyid, tvr->notifyid); |
| + |
| + ret = trusty_std_call32(tctx->dev->parent, SMC_SC_VDEV_KICK_VQ, |
| + tvdev->notifyid, tvr->notifyid, 0); |
| + if (ret) { |
| + dev_err(tctx->dev, "vq notify (%d, %d) returned %d\n", |
| + tvdev->notifyid, tvr->notifyid, ret); |
| + } |
| +} |
| + |
| +static void kick_vqs(struct work_struct *work) |
| +{ |
| + unsigned int i; |
| + struct trusty_vdev *tvdev; |
| + struct trusty_ctx *tctx = container_of(work, struct trusty_ctx, |
| + kick_vqs); |
| + mutex_lock(&tctx->mlock); |
| + list_for_each_entry(tvdev, &tctx->vdev_list, node) { |
| + for (i = 0; i < tvdev->vring_num; i++) { |
| + struct trusty_vring *tvr = &tvdev->vrings[i]; |
| + |
| + if (atomic_xchg(&tvr->needs_kick, 0)) |
| + kick_vq(tctx, tvdev, tvr); |
| + } |
| + } |
| + mutex_unlock(&tctx->mlock); |
| +} |
| + |
| +static bool trusty_virtio_notify(struct virtqueue *vq) |
| +{ |
| + struct trusty_vring *tvr = vq->priv; |
| + struct trusty_vdev *tvdev = tvr->tvdev; |
| + struct trusty_ctx *tctx = tvdev->tctx; |
| + u32 api_ver = trusty_get_api_version(tctx->dev->parent); |
| + |
| + if (api_ver < TRUSTY_API_VERSION_SMP_NOP) { |
| + atomic_set(&tvr->needs_kick, 1); |
| + queue_work(tctx->kick_wq, &tctx->kick_vqs); |
| + } else { |
| + trusty_enqueue_nop(tctx->dev->parent, &tvr->kick_nop); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +static int trusty_load_device_descr(struct trusty_ctx *tctx, |
| + trusty_shared_mem_id_t id, size_t sz) |
| +{ |
| + int ret; |
| + |
| + dev_dbg(tctx->dev, "%s: %zu bytes @ id %llu\n", __func__, sz, id); |
| + |
| + ret = trusty_std_call32(tctx->dev->parent, SMC_SC_VIRTIO_GET_DESCR, |
| + (u32)id, id >> 32, sz); |
| + if (ret < 0) { |
| + dev_err(tctx->dev, "%s: virtio get descr returned (%d)\n", |
| + __func__, ret); |
| + return -ENODEV; |
| + } |
| + return ret; |
| +} |
| + |
| +static void trusty_virtio_stop(struct trusty_ctx *tctx, |
| + trusty_shared_mem_id_t id, size_t sz) |
| +{ |
| + int ret; |
| + |
| + dev_dbg(tctx->dev, "%s: %zu bytes @ id %llu\n", __func__, sz, id); |
| + |
| + ret = trusty_std_call32(tctx->dev->parent, SMC_SC_VIRTIO_STOP, |
| + (u32)id, id >> 32, sz); |
| + if (ret) { |
| + dev_err(tctx->dev, "%s: virtio done returned (%d)\n", |
| + __func__, ret); |
| + return; |
| + } |
| +} |
| + |
| +static int trusty_virtio_start(struct trusty_ctx *tctx, |
| + trusty_shared_mem_id_t id, size_t sz) |
| +{ |
| + int ret; |
| + |
| + dev_dbg(tctx->dev, "%s: %zu bytes @ id %llu\n", __func__, sz, id); |
| + |
| + ret = trusty_std_call32(tctx->dev->parent, SMC_SC_VIRTIO_START, |
| + (u32)id, id >> 32, sz); |
| + if (ret) { |
| + dev_err(tctx->dev, "%s: virtio start returned (%d)\n", |
| + __func__, ret); |
| + return -ENODEV; |
| + } |
| + return 0; |
| +} |
| + |
| +static void trusty_virtio_reset(struct virtio_device *vdev) |
| +{ |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + struct trusty_ctx *tctx = tvdev->tctx; |
| + |
| + dev_dbg(&vdev->dev, "reset vdev_id=%d\n", tvdev->notifyid); |
| + trusty_std_call32(tctx->dev->parent, SMC_SC_VDEV_RESET, |
| + tvdev->notifyid, 0, 0); |
| +} |
| + |
| +static u64 trusty_virtio_get_features(struct virtio_device *vdev) |
| +{ |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + |
| + return tvdev->vdev_descr->dfeatures | |
| + (1ULL << VIRTIO_F_ACCESS_PLATFORM); |
| +} |
| + |
| +static int trusty_virtio_finalize_features(struct virtio_device *vdev) |
| +{ |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + u64 features = vdev->features; |
| + |
| + /* |
| + * We set VIRTIO_F_ACCESS_PLATFORM to enable the dma mapping hooks. |
| + * The other side does not need to know. |
| + */ |
| + features &= ~(1ULL << VIRTIO_F_ACCESS_PLATFORM); |
| + |
| + /* Make sure we don't have any features > 32 bits! */ |
| + if (WARN_ON((u32)vdev->features != features)) |
| + return -EINVAL; |
| + |
| + tvdev->vdev_descr->gfeatures = vdev->features; |
| + return 0; |
| +} |
| + |
| +static void trusty_virtio_get_config(struct virtio_device *vdev, |
| + unsigned int offset, void *buf, |
| + unsigned int len) |
| +{ |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + |
| + dev_dbg(&vdev->dev, "%s: %d bytes @ offset %d\n", |
| + __func__, len, offset); |
| + |
| + if (tvdev->config) { |
| + if (offset + len <= tvdev->config_len) |
| + memcpy(buf, tvdev->config + offset, len); |
| + } |
| +} |
| + |
| +static void trusty_virtio_set_config(struct virtio_device *vdev, |
| + unsigned int offset, const void *buf, |
| + unsigned int len) |
| +{ |
| +} |
| + |
| +static u8 trusty_virtio_get_status(struct virtio_device *vdev) |
| +{ |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + |
| + return tvdev->vdev_descr->status; |
| +} |
| + |
| +static void trusty_virtio_set_status(struct virtio_device *vdev, u8 status) |
| +{ |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + |
| + tvdev->vdev_descr->status = status; |
| +} |
| + |
| +static void _del_vqs(struct virtio_device *vdev) |
| +{ |
| + unsigned int i; |
| + int ret; |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + struct trusty_vring *tvr = &tvdev->vrings[0]; |
| + |
| + for (i = 0; i < tvdev->vring_num; i++, tvr++) { |
| + /* dequeue kick_nop */ |
| + trusty_dequeue_nop(tvdev->tctx->dev->parent, &tvr->kick_nop); |
| + |
| + /* delete vq */ |
| + if (tvr->vq) { |
| + vring_del_virtqueue(tvr->vq); |
| + tvr->vq = NULL; |
| + } |
| + /* delete vring */ |
| + if (tvr->vaddr) { |
| + ret = trusty_reclaim_memory(tvdev->tctx->dev->parent, |
| + tvr->shared_mem_id, |
| + &tvr->sg, 1); |
| + if (WARN_ON(ret)) { |
| + dev_err(&vdev->dev, |
| + "trusty_revoke_memory failed: %d 0x%llx\n", |
| + ret, tvr->shared_mem_id); |
| + /* |
| + * It is not safe to free this memory if |
| + * trusty_revoke_memory fails. Leak it in that |
| + * case. |
| + */ |
| + } else { |
| + free_pages_exact(tvr->vaddr, tvr->size); |
| + } |
| + tvr->vaddr = NULL; |
| + } |
| + } |
| +} |
| + |
| +static void trusty_virtio_del_vqs(struct virtio_device *vdev) |
| +{ |
| + _del_vqs(vdev); |
| +} |
| + |
| + |
| +static struct virtqueue *_find_vq(struct virtio_device *vdev, |
| + unsigned int id, |
| + void (*callback)(struct virtqueue *vq), |
| + const char *name, |
| + bool ctx) |
| +{ |
| + struct trusty_vring *tvr; |
| + struct trusty_vdev *tvdev = vdev_to_tvdev(vdev); |
| + phys_addr_t pa; |
| + int ret; |
| + |
| + if (!name) |
| + return ERR_PTR(-EINVAL); |
| + |
| + if (id >= tvdev->vring_num) |
| + return ERR_PTR(-EINVAL); |
| + |
| + tvr = &tvdev->vrings[id]; |
| + |
| + /* actual size of vring (in bytes) */ |
| + tvr->size = PAGE_ALIGN(vring_size(tvr->elem_num, tvr->align)); |
| + |
| + /* allocate memory for the vring. */ |
| + tvr->vaddr = alloc_pages_exact(tvr->size, GFP_KERNEL | __GFP_ZERO); |
| + if (!tvr->vaddr) { |
| + dev_err(&vdev->dev, "vring alloc failed\n"); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + |
| + sg_init_one(&tvr->sg, tvr->vaddr, tvr->size); |
| + ret = trusty_share_memory_compat(tvdev->tctx->dev->parent, |
| + &tvr->shared_mem_id, &tvr->sg, 1, |
| + PAGE_KERNEL); |
| + if (ret) { |
| + pa = virt_to_phys(tvr->vaddr); |
| + dev_err(&vdev->dev, "trusty_share_memory failed: %d %pa\n", |
| + ret, &pa); |
| + goto err_share_memory; |
| + } |
| + |
| + /* save vring address to shared structure */ |
| + tvr->vr_descr->da = (u32)tvr->shared_mem_id; |
| + |
| + /* da field is only 32 bit wide. Use previously unused 'reserved' field |
| + * to store top 32 bits of 64-bit shared_mem_id |
| + */ |
| + tvr->vr_descr->pa = (u32)(tvr->shared_mem_id >> 32); |
| + |
| + dev_info(&vdev->dev, "vring%d: va(id) %p(%llx) qsz %d notifyid %d\n", |
| + id, tvr->vaddr, (u64)tvr->shared_mem_id, tvr->elem_num, |
| + tvr->notifyid); |
| + |
| + tvr->vq = vring_new_virtqueue(id, tvr->elem_num, tvr->align, |
| + vdev, true, ctx, tvr->vaddr, |
| + trusty_virtio_notify, callback, name); |
| + if (!tvr->vq) { |
| + dev_err(&vdev->dev, "vring_new_virtqueue %s failed\n", |
| + name); |
| + goto err_new_virtqueue; |
| + } |
| + |
| + tvr->vq->priv = tvr; |
| + |
| + return tvr->vq; |
| + |
| +err_new_virtqueue: |
| + ret = trusty_reclaim_memory(tvdev->tctx->dev->parent, |
| + tvr->shared_mem_id, &tvr->sg, 1); |
| + if (WARN_ON(ret)) { |
| + dev_err(&vdev->dev, "trusty_revoke_memory failed: %d 0x%llx\n", |
| + ret, tvr->shared_mem_id); |
| + /* |
| + * It is not safe to free this memory if trusty_revoke_memory |
| + * fails. Leak it in that case. |
| + */ |
| + } else { |
| +err_share_memory: |
| + free_pages_exact(tvr->vaddr, tvr->size); |
| + } |
| + tvr->vaddr = NULL; |
| + return ERR_PTR(-ENOMEM); |
| +} |
| + |
| +static int trusty_virtio_find_vqs(struct virtio_device *vdev, unsigned int nvqs, |
| + struct virtqueue *vqs[], |
| + vq_callback_t *callbacks[], |
| + const char * const names[], |
| + const bool *ctxs, |
| + struct irq_affinity *desc) |
| +{ |
| + unsigned int i; |
| + int ret; |
| + bool ctx = false; |
| + |
| + for (i = 0; i < nvqs; i++) { |
| + ctx = false; |
| + if (ctxs) |
| + ctx = ctxs[i]; |
| + vqs[i] = _find_vq(vdev, i, callbacks[i], names[i], ctx); |
| + if (IS_ERR(vqs[i])) { |
| + ret = PTR_ERR(vqs[i]); |
| + _del_vqs(vdev); |
| + return ret; |
| + } |
| + } |
| + return 0; |
| +} |
| + |
| +static const char *trusty_virtio_bus_name(struct virtio_device *vdev) |
| +{ |
| + return "trusty-virtio"; |
| +} |
| + |
| +/* The ops structure which hooks everything together. */ |
| +static const struct virtio_config_ops trusty_virtio_config_ops = { |
| + .get_features = trusty_virtio_get_features, |
| + .finalize_features = trusty_virtio_finalize_features, |
| + .get = trusty_virtio_get_config, |
| + .set = trusty_virtio_set_config, |
| + .get_status = trusty_virtio_get_status, |
| + .set_status = trusty_virtio_set_status, |
| + .reset = trusty_virtio_reset, |
| + .find_vqs = trusty_virtio_find_vqs, |
| + .del_vqs = trusty_virtio_del_vqs, |
| + .bus_name = trusty_virtio_bus_name, |
| +}; |
| + |
| +static int trusty_virtio_add_device(struct trusty_ctx *tctx, |
| + struct fw_rsc_vdev *vdev_descr, |
| + struct fw_rsc_vdev_vring *vr_descr, |
| + void *config) |
| +{ |
| + int i, ret; |
| + struct trusty_vdev *tvdev; |
| + |
| + tvdev = kzalloc(struct_size(tvdev, vrings, vdev_descr->num_of_vrings), |
| + GFP_KERNEL); |
| + if (!tvdev) |
| + return -ENOMEM; |
| + |
| + /* setup vdev */ |
| + tvdev->tctx = tctx; |
| + tvdev->vdev.dev.parent = tctx->dev; |
| + tvdev->vdev.id.device = vdev_descr->id; |
| + tvdev->vdev.config = &trusty_virtio_config_ops; |
| + tvdev->vdev_descr = vdev_descr; |
| + tvdev->notifyid = vdev_descr->notifyid; |
| + |
| + /* setup config */ |
| + tvdev->config = config; |
| + tvdev->config_len = vdev_descr->config_len; |
| + |
| + /* setup vrings and vdev resource */ |
| + tvdev->vring_num = vdev_descr->num_of_vrings; |
| + |
| + for (i = 0; i < tvdev->vring_num; i++, vr_descr++) { |
| + struct trusty_vring *tvr = &tvdev->vrings[i]; |
| + |
| + tvr->tvdev = tvdev; |
| + tvr->vr_descr = vr_descr; |
| + tvr->align = vr_descr->align; |
| + tvr->elem_num = vr_descr->num; |
| + tvr->notifyid = vr_descr->notifyid; |
| + trusty_nop_init(&tvr->kick_nop, SMC_NC_VDEV_KICK_VQ, |
| + tvdev->notifyid, tvr->notifyid); |
| + } |
| + |
| + /* register device */ |
| + ret = register_virtio_device(&tvdev->vdev); |
| + if (ret) { |
| + dev_err(tctx->dev, |
| + "Failed (%d) to register device dev type %u\n", |
| + ret, vdev_descr->id); |
| + goto err_register; |
| + } |
| + |
| + /* add it to tracking list */ |
| + list_add_tail(&tvdev->node, &tctx->vdev_list); |
| + |
| + return 0; |
| + |
| +err_register: |
| + kfree(tvdev); |
| + return ret; |
| +} |
| + |
| +static int trusty_parse_device_descr(struct trusty_ctx *tctx, |
| + void *descr_va, size_t descr_sz) |
| +{ |
| + u32 i; |
| + struct resource_table *descr = descr_va; |
| + |
| + if (descr_sz < sizeof(*descr)) { |
| + dev_err(tctx->dev, "descr table is too small (0x%x)\n", |
| + (int)descr_sz); |
| + return -ENODEV; |
| + } |
| + |
| + if (descr->ver != RSC_DESCR_VER) { |
| + dev_err(tctx->dev, "unexpected descr ver (0x%x)\n", |
| + (int)descr->ver); |
| + return -ENODEV; |
| + } |
| + |
| + if (descr_sz < (sizeof(*descr) + descr->num * sizeof(u32))) { |
| + dev_err(tctx->dev, "descr table is too small (0x%x)\n", |
| + (int)descr->ver); |
| + return -ENODEV; |
| + } |
| + |
| + for (i = 0; i < descr->num; i++) { |
| + struct fw_rsc_hdr *hdr; |
| + struct fw_rsc_vdev *vd; |
| + struct fw_rsc_vdev_vring *vr; |
| + void *cfg; |
| + size_t vd_sz; |
| + |
| + u32 offset = descr->offset[i]; |
| + |
| + if (offset >= descr_sz) { |
| + dev_err(tctx->dev, "offset is out of bounds (%u)\n", |
| + offset); |
| + return -ENODEV; |
| + } |
| + |
| + /* check space for rsc header */ |
| + if ((descr_sz - offset) < sizeof(struct fw_rsc_hdr)) { |
| + dev_err(tctx->dev, "no space for rsc header (%u)\n", |
| + offset); |
| + return -ENODEV; |
| + } |
| + hdr = (struct fw_rsc_hdr *)((u8 *)descr + offset); |
| + offset += sizeof(struct fw_rsc_hdr); |
| + |
| + /* check type */ |
| + if (hdr->type != RSC_VDEV) { |
| + dev_err(tctx->dev, "unsupported rsc type (%u)\n", |
| + hdr->type); |
| + continue; |
| + } |
| + |
| + /* got vdev: check space for vdev */ |
| + if ((descr_sz - offset) < sizeof(struct fw_rsc_vdev)) { |
| + dev_err(tctx->dev, "no space for vdev descr (%u)\n", |
| + offset); |
| + return -ENODEV; |
| + } |
| + vd = (struct fw_rsc_vdev *)((u8 *)descr + offset); |
| + |
| + /* check space for vrings and config area */ |
| + vd_sz = sizeof(struct fw_rsc_vdev) + |
| + vd->num_of_vrings * sizeof(struct fw_rsc_vdev_vring) + |
| + vd->config_len; |
| + |
| + if ((descr_sz - offset) < vd_sz) { |
| + dev_err(tctx->dev, "no space for vdev (%u)\n", offset); |
| + return -ENODEV; |
| + } |
| + vr = (struct fw_rsc_vdev_vring *)vd->vring; |
| + cfg = (void *)(vr + vd->num_of_vrings); |
| + |
| + trusty_virtio_add_device(tctx, vd, vr, cfg); |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static void _remove_devices_locked(struct trusty_ctx *tctx) |
| +{ |
| + struct trusty_vdev *tvdev, *next; |
| + |
| + list_for_each_entry_safe(tvdev, next, &tctx->vdev_list, node) { |
| + list_del(&tvdev->node); |
| + unregister_virtio_device(&tvdev->vdev); |
| + kfree(tvdev); |
| + } |
| +} |
| + |
| +static void trusty_virtio_remove_devices(struct trusty_ctx *tctx) |
| +{ |
| + mutex_lock(&tctx->mlock); |
| + _remove_devices_locked(tctx); |
| + mutex_unlock(&tctx->mlock); |
| +} |
| + |
| +static int trusty_virtio_add_devices(struct trusty_ctx *tctx) |
| +{ |
| + int ret; |
| + int ret_tmp; |
| + void *descr_va; |
| + trusty_shared_mem_id_t descr_id; |
| + size_t descr_sz; |
| + size_t descr_buf_sz; |
| + |
| + /* allocate buffer to load device descriptor into */ |
| + descr_buf_sz = PAGE_SIZE; |
| + descr_va = alloc_pages_exact(descr_buf_sz, GFP_KERNEL | __GFP_ZERO); |
| + if (!descr_va) { |
| + dev_err(tctx->dev, "Failed to allocate shared area\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + sg_init_one(&tctx->shared_sg, descr_va, descr_buf_sz); |
| + ret = trusty_share_memory(tctx->dev->parent, &descr_id, |
| + &tctx->shared_sg, 1, PAGE_KERNEL); |
| + if (ret) { |
| + dev_err(tctx->dev, "trusty_share_memory failed: %d\n", ret); |
| + goto err_share_memory; |
| + } |
| + |
| + /* load device descriptors */ |
| + ret = trusty_load_device_descr(tctx, descr_id, descr_buf_sz); |
| + if (ret < 0) { |
| + dev_err(tctx->dev, "failed (%d) to load device descr\n", ret); |
| + goto err_load_descr; |
| + } |
| + |
| + descr_sz = (size_t)ret; |
| + |
| + mutex_lock(&tctx->mlock); |
| + |
| + /* parse device descriptor and add virtio devices */ |
| + ret = trusty_parse_device_descr(tctx, descr_va, descr_sz); |
| + if (ret) { |
| + dev_err(tctx->dev, "failed (%d) to parse device descr\n", ret); |
| + goto err_parse_descr; |
| + } |
| + |
| + /* register call notifier */ |
| + ret = trusty_call_notifier_register(tctx->dev->parent, |
| + &tctx->call_notifier); |
| + if (ret) { |
| + dev_err(tctx->dev, "%s: failed (%d) to register notifier\n", |
| + __func__, ret); |
| + goto err_register_notifier; |
| + } |
| + |
| + /* start virtio */ |
| + ret = trusty_virtio_start(tctx, descr_id, descr_sz); |
| + if (ret) { |
| + dev_err(tctx->dev, "failed (%d) to start virtio\n", ret); |
| + goto err_start_virtio; |
| + } |
| + |
| + /* attach shared area */ |
| + tctx->shared_va = descr_va; |
| + tctx->shared_id = descr_id; |
| + tctx->shared_sz = descr_buf_sz; |
| + |
| + mutex_unlock(&tctx->mlock); |
| + |
| + return 0; |
| + |
| +err_start_virtio: |
| + trusty_call_notifier_unregister(tctx->dev->parent, |
| + &tctx->call_notifier); |
| + cancel_work_sync(&tctx->check_vqs); |
| +err_register_notifier: |
| +err_parse_descr: |
| + _remove_devices_locked(tctx); |
| + mutex_unlock(&tctx->mlock); |
| + cancel_work_sync(&tctx->kick_vqs); |
| + trusty_virtio_stop(tctx, descr_id, descr_sz); |
| +err_load_descr: |
| + ret_tmp = trusty_reclaim_memory(tctx->dev->parent, descr_id, |
| + &tctx->shared_sg, 1); |
| + if (WARN_ON(ret_tmp)) { |
| + dev_err(tctx->dev, "trusty_revoke_memory failed: %d 0x%llx\n", |
| + ret_tmp, tctx->shared_id); |
| + /* |
| + * It is not safe to free this memory if trusty_revoke_memory |
| + * fails. Leak it in that case. |
| + */ |
| + } else { |
| +err_share_memory: |
| + free_pages_exact(descr_va, descr_buf_sz); |
| + } |
| + return ret; |
| +} |
| + |
| +static dma_addr_t trusty_virtio_dma_map_page(struct device *dev, |
| + struct page *page, |
| + unsigned long offset, size_t size, |
| + enum dma_data_direction dir, |
| + unsigned long attrs) |
| +{ |
| + struct tipc_msg_buf *buf = page_to_virt(page) + offset; |
| + |
| + return buf->buf_id; |
| +} |
| + |
| +static const struct dma_map_ops trusty_virtio_dma_map_ops = { |
| + .map_page = trusty_virtio_dma_map_page, |
| +}; |
| + |
| +static int trusty_virtio_probe(struct platform_device *pdev) |
| +{ |
| + int ret; |
| + struct trusty_ctx *tctx; |
| + |
| + tctx = kzalloc(sizeof(*tctx), GFP_KERNEL); |
| + if (!tctx) |
| + return -ENOMEM; |
| + |
| + tctx->dev = &pdev->dev; |
| + tctx->call_notifier.notifier_call = trusty_call_notify; |
| + mutex_init(&tctx->mlock); |
| + INIT_LIST_HEAD(&tctx->vdev_list); |
| + INIT_WORK(&tctx->check_vqs, check_all_vqs); |
| + INIT_WORK(&tctx->kick_vqs, kick_vqs); |
| + platform_set_drvdata(pdev, tctx); |
| + |
| + set_dma_ops(&pdev->dev, &trusty_virtio_dma_map_ops); |
| + |
| + tctx->check_wq = alloc_workqueue("trusty-check-wq", WQ_UNBOUND, 0); |
| + if (!tctx->check_wq) { |
| + ret = -ENODEV; |
| + dev_err(&pdev->dev, "Failed create trusty-check-wq\n"); |
| + goto err_create_check_wq; |
| + } |
| + |
| + tctx->kick_wq = alloc_workqueue("trusty-kick-wq", |
| + WQ_UNBOUND | WQ_CPU_INTENSIVE, 0); |
| + if (!tctx->kick_wq) { |
| + ret = -ENODEV; |
| + dev_err(&pdev->dev, "Failed create trusty-kick-wq\n"); |
| + goto err_create_kick_wq; |
| + } |
| + |
| + ret = trusty_virtio_add_devices(tctx); |
| + if (ret) { |
| + dev_err(&pdev->dev, "Failed to add virtio devices\n"); |
| + goto err_add_devices; |
| + } |
| + |
| + dev_info(&pdev->dev, "initializing done\n"); |
| + return 0; |
| + |
| +err_add_devices: |
| + destroy_workqueue(tctx->kick_wq); |
| +err_create_kick_wq: |
| + destroy_workqueue(tctx->check_wq); |
| +err_create_check_wq: |
| + kfree(tctx); |
| + return ret; |
| +} |
| + |
| +static int trusty_virtio_remove(struct platform_device *pdev) |
| +{ |
| + struct trusty_ctx *tctx = platform_get_drvdata(pdev); |
| + int ret; |
| + |
| + /* unregister call notifier and wait until workqueue is done */ |
| + trusty_call_notifier_unregister(tctx->dev->parent, |
| + &tctx->call_notifier); |
| + cancel_work_sync(&tctx->check_vqs); |
| + |
| + /* remove virtio devices */ |
| + trusty_virtio_remove_devices(tctx); |
| + cancel_work_sync(&tctx->kick_vqs); |
| + |
| + /* destroy workqueues */ |
| + destroy_workqueue(tctx->kick_wq); |
| + destroy_workqueue(tctx->check_wq); |
| + |
| + /* notify remote that shared area goes away */ |
| + trusty_virtio_stop(tctx, tctx->shared_id, tctx->shared_sz); |
| + |
| + /* free shared area */ |
| + ret = trusty_reclaim_memory(tctx->dev->parent, tctx->shared_id, |
| + &tctx->shared_sg, 1); |
| + if (WARN_ON(ret)) { |
| + dev_err(tctx->dev, "trusty_revoke_memory failed: %d 0x%llx\n", |
| + ret, tctx->shared_id); |
| + /* |
| + * It is not safe to free this memory if trusty_revoke_memory |
| + * fails. Leak it in that case. |
| + */ |
| + } else { |
| + free_pages_exact(tctx->shared_va, tctx->shared_sz); |
| + } |
| + |
| + /* free context */ |
| + kfree(tctx); |
| + return 0; |
| +} |
| + |
| +static const struct of_device_id trusty_of_match[] = { |
| + { |
| + .compatible = "android,trusty-virtio-v1", |
| + }, |
| + {}, |
| +}; |
| + |
| +MODULE_DEVICE_TABLE(of, trusty_of_match); |
| + |
| +static struct platform_driver trusty_virtio_driver = { |
| + .probe = trusty_virtio_probe, |
| + .remove = trusty_virtio_remove, |
| + .driver = { |
| + .name = "trusty-virtio", |
| + .of_match_table = trusty_of_match, |
| + }, |
| +}; |
| + |
| +module_platform_driver(trusty_virtio_driver); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_DESCRIPTION("Trusty virtio driver"); |
| +/* |
| + * TODO(b/168322325): trusty-virtio and trusty-ipc should be independent. |
| + * However, trusty-virtio is not completely generic and is aware of trusty-ipc. |
| + * See header includes. Particularly, trusty-virtio.ko can't be loaded before |
| + * trusty-ipc.ko. |
| + */ |
| +MODULE_SOFTDEP("pre: trusty-ipc"); |
| diff --git a/drivers/trusty/trusty.c b/drivers/trusty/trusty.c |
| new file mode 100644 |
| index 000000000000..265eab52aea0 |
| --- /dev/null |
| +++ b/drivers/trusty/trusty.c |
| @@ -0,0 +1,981 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * Copyright (C) 2013 Google, Inc. |
| + */ |
| + |
| +#include <linux/delay.h> |
| +#include <linux/module.h> |
| +#include <linux/of.h> |
| +#include <linux/of_platform.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/slab.h> |
| +#include <linux/stat.h> |
| +#include <linux/string.h> |
| +#include <linux/trusty/arm_ffa.h> |
| +#include <linux/trusty/smcall.h> |
| +#include <linux/trusty/sm_err.h> |
| +#include <linux/trusty/trusty.h> |
| + |
| +#include <linux/scatterlist.h> |
| +#include <linux/dma-mapping.h> |
| + |
| +#include "trusty-smc.h" |
| + |
| +struct trusty_state; |
| +static struct platform_driver trusty_driver; |
| + |
| +struct trusty_work { |
| + struct trusty_state *ts; |
| + struct work_struct work; |
| +}; |
| + |
| +struct trusty_state { |
| + struct mutex smc_lock; |
| + struct atomic_notifier_head notifier; |
| + struct completion cpu_idle_completion; |
| + char *version_str; |
| + u32 api_version; |
| + bool trusty_panicked; |
| + struct device *dev; |
| + struct workqueue_struct *nop_wq; |
| + struct trusty_work __percpu *nop_works; |
| + struct list_head nop_queue; |
| + spinlock_t nop_lock; /* protects nop_queue */ |
| + struct device_dma_parameters dma_parms; |
| + void *ffa_tx; |
| + void *ffa_rx; |
| + u16 ffa_local_id; |
| + u16 ffa_remote_id; |
| + struct mutex share_memory_msg_lock; /* protects share_memory_msg */ |
| +}; |
| + |
| +static inline unsigned long smc(unsigned long r0, unsigned long r1, |
| + unsigned long r2, unsigned long r3) |
| +{ |
| + return trusty_smc8(r0, r1, r2, r3, 0, 0, 0, 0).r0; |
| +} |
| + |
| +s32 trusty_fast_call32(struct device *dev, u32 smcnr, u32 a0, u32 a1, u32 a2) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + if (WARN_ON(!s)) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + if (WARN_ON(!SMC_IS_FASTCALL(smcnr))) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + if (WARN_ON(SMC_IS_SMC64(smcnr))) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + |
| + return smc(smcnr, a0, a1, a2); |
| +} |
| +EXPORT_SYMBOL(trusty_fast_call32); |
| + |
| +#ifdef CONFIG_64BIT |
| +s64 trusty_fast_call64(struct device *dev, u64 smcnr, u64 a0, u64 a1, u64 a2) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + if (WARN_ON(!s)) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + if (WARN_ON(!SMC_IS_FASTCALL(smcnr))) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + if (WARN_ON(!SMC_IS_SMC64(smcnr))) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + |
| + return smc(smcnr, a0, a1, a2); |
| +} |
| +EXPORT_SYMBOL(trusty_fast_call64); |
| +#endif |
| + |
| +static unsigned long trusty_std_call_inner(struct device *dev, |
| + unsigned long smcnr, |
| + unsigned long a0, unsigned long a1, |
| + unsigned long a2) |
| +{ |
| + unsigned long ret; |
| + int retry = 5; |
| + |
| + dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx)\n", |
| + __func__, smcnr, a0, a1, a2); |
| + while (true) { |
| + ret = smc(smcnr, a0, a1, a2); |
| + while ((s32)ret == SM_ERR_FIQ_INTERRUPTED) |
| + ret = smc(SMC_SC_RESTART_FIQ, 0, 0, 0); |
| + if ((int)ret != SM_ERR_BUSY || !retry) |
| + break; |
| + |
| + dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy, retry\n", |
| + __func__, smcnr, a0, a1, a2); |
| + retry--; |
| + } |
| + |
| + return ret; |
| +} |
| + |
| +static unsigned long trusty_std_call_helper(struct device *dev, |
| + unsigned long smcnr, |
| + unsigned long a0, unsigned long a1, |
| + unsigned long a2) |
| +{ |
| + unsigned long ret; |
| + int sleep_time = 1; |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + while (true) { |
| + local_irq_disable(); |
| + atomic_notifier_call_chain(&s->notifier, TRUSTY_CALL_PREPARE, |
| + NULL); |
| + ret = trusty_std_call_inner(dev, smcnr, a0, a1, a2); |
| + if (ret == SM_ERR_PANIC) { |
| + s->trusty_panicked = true; |
| + if (IS_ENABLED(CONFIG_TRUSTY_CRASH_IS_PANIC)) |
| + panic("trusty crashed"); |
| + else |
| + WARN_ONCE(1, "trusty crashed"); |
| + } |
| + |
| + atomic_notifier_call_chain(&s->notifier, TRUSTY_CALL_RETURNED, |
| + NULL); |
| + if (ret == SM_ERR_INTERRUPTED) { |
| + /* |
| + * Make sure this cpu will eventually re-enter trusty |
| + * even if the std_call resumes on another cpu. |
| + */ |
| + trusty_enqueue_nop(dev, NULL); |
| + } |
| + local_irq_enable(); |
| + |
| + if ((int)ret != SM_ERR_BUSY) |
| + break; |
| + |
| + if (sleep_time == 256) |
| + dev_warn(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy\n", |
| + __func__, smcnr, a0, a1, a2); |
| + dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) returned busy, wait %d ms\n", |
| + __func__, smcnr, a0, a1, a2, sleep_time); |
| + |
| + msleep(sleep_time); |
| + if (sleep_time < 1000) |
| + sleep_time <<= 1; |
| + |
| + dev_dbg(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) retry\n", |
| + __func__, smcnr, a0, a1, a2); |
| + } |
| + |
| + if (sleep_time > 256) |
| + dev_warn(dev, "%s(0x%lx 0x%lx 0x%lx 0x%lx) busy cleared\n", |
| + __func__, smcnr, a0, a1, a2); |
| + |
| + return ret; |
| +} |
| + |
| +static void trusty_std_call_cpu_idle(struct trusty_state *s) |
| +{ |
| + int ret; |
| + |
| + ret = wait_for_completion_timeout(&s->cpu_idle_completion, HZ * 10); |
| + if (!ret) { |
| + dev_warn(s->dev, |
| + "%s: timed out waiting for cpu idle to clear, retry anyway\n", |
| + __func__); |
| + } |
| +} |
| + |
| +s32 trusty_std_call32(struct device *dev, u32 smcnr, u32 a0, u32 a1, u32 a2) |
| +{ |
| + int ret; |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + if (WARN_ON(SMC_IS_FASTCALL(smcnr))) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + |
| + if (WARN_ON(SMC_IS_SMC64(smcnr))) |
| + return SM_ERR_INVALID_PARAMETERS; |
| + |
| + if (s->trusty_panicked) { |
| + /* |
| + * Avoid calling the notifiers if trusty has panicked as they |
| + * can trigger more calls. |
| + */ |
| + return SM_ERR_PANIC; |
| + } |
| + |
| + if (smcnr != SMC_SC_NOP) { |
| + mutex_lock(&s->smc_lock); |
| + reinit_completion(&s->cpu_idle_completion); |
| + } |
| + |
| + dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) started\n", |
| + __func__, smcnr, a0, a1, a2); |
| + |
| + ret = trusty_std_call_helper(dev, smcnr, a0, a1, a2); |
| + while (ret == SM_ERR_INTERRUPTED || ret == SM_ERR_CPU_IDLE) { |
| + dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) interrupted\n", |
| + __func__, smcnr, a0, a1, a2); |
| + if (ret == SM_ERR_CPU_IDLE) |
| + trusty_std_call_cpu_idle(s); |
| + ret = trusty_std_call_helper(dev, SMC_SC_RESTART_LAST, 0, 0, 0); |
| + } |
| + dev_dbg(dev, "%s(0x%x 0x%x 0x%x 0x%x) returned 0x%x\n", |
| + __func__, smcnr, a0, a1, a2, ret); |
| + |
| + if (smcnr == SMC_SC_NOP) |
| + complete(&s->cpu_idle_completion); |
| + else |
| + mutex_unlock(&s->smc_lock); |
| + |
| + return ret; |
| +} |
| +EXPORT_SYMBOL(trusty_std_call32); |
| + |
| +int trusty_share_memory(struct device *dev, u64 *id, |
| + struct scatterlist *sglist, unsigned int nents, |
| + pgprot_t pgprot) |
| +{ |
| + return trusty_transfer_memory(dev, id, sglist, nents, pgprot, 0, |
| + false); |
| +} |
| +EXPORT_SYMBOL(trusty_share_memory); |
| + |
| +int trusty_transfer_memory(struct device *dev, u64 *id, |
| + struct scatterlist *sglist, unsigned int nents, |
| + pgprot_t pgprot, u64 tag, bool lend) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + int ret; |
| + struct ns_mem_page_info pg_inf; |
| + struct scatterlist *sg; |
| + size_t count; |
| + size_t i; |
| + size_t len; |
| + u64 ffa_handle = 0; |
| + size_t total_len; |
| + size_t endpoint_count = 1; |
| + struct ffa_mtd *mtd = s->ffa_tx; |
| + size_t comp_mrd_offset = offsetof(struct ffa_mtd, emad[endpoint_count]); |
| + struct ffa_comp_mrd *comp_mrd = s->ffa_tx + comp_mrd_offset; |
| + struct ffa_cons_mrd *cons_mrd = comp_mrd->address_range_array; |
| + size_t cons_mrd_offset = (void *)cons_mrd - s->ffa_tx; |
| + struct smc_ret8 smc_ret; |
| + u32 cookie_low; |
| + u32 cookie_high; |
| + |
| + if (WARN_ON(dev->driver != &trusty_driver.driver)) |
| + return -EINVAL; |
| + |
| + if (WARN_ON(nents < 1)) |
| + return -EINVAL; |
| + |
| + if (nents != 1 && s->api_version < TRUSTY_API_VERSION_MEM_OBJ) { |
| + dev_err(s->dev, "%s: old trusty version does not support non-contiguous memory objects\n", |
| + __func__); |
| + return -EOPNOTSUPP; |
| + } |
| + |
| + count = dma_map_sg(dev, sglist, nents, DMA_BIDIRECTIONAL); |
| + if (count != nents) { |
| + dev_err(s->dev, "failed to dma map sg_table\n"); |
| + return -EINVAL; |
| + } |
| + |
| + sg = sglist; |
| + ret = trusty_encode_page_info(&pg_inf, phys_to_page(sg_dma_address(sg)), |
| + pgprot); |
| + if (ret) { |
| + dev_err(s->dev, "%s: trusty_encode_page_info failed\n", |
| + __func__); |
| + goto err_encode_page_info; |
| + } |
| + |
| + if (s->api_version < TRUSTY_API_VERSION_MEM_OBJ) { |
| + *id = pg_inf.compat_attr; |
| + return 0; |
| + } |
| + |
| + len = 0; |
| + for_each_sg(sglist, sg, nents, i) |
| + len += sg_dma_len(sg); |
| + |
| + mutex_lock(&s->share_memory_msg_lock); |
| + |
| + mtd->sender_id = s->ffa_local_id; |
| + mtd->memory_region_attributes = pg_inf.ffa_mem_attr; |
| + mtd->reserved_3 = 0; |
| + mtd->flags = 0; |
| + mtd->handle = 0; |
| + mtd->tag = tag; |
| + mtd->reserved_24_27 = 0; |
| + mtd->emad_count = endpoint_count; |
| + for (i = 0; i < endpoint_count; i++) { |
| + struct ffa_emad *emad = &mtd->emad[i]; |
| + /* TODO: support stream ids */ |
| + emad->mapd.endpoint_id = s->ffa_remote_id; |
| + emad->mapd.memory_access_permissions = pg_inf.ffa_mem_perm; |
| + emad->mapd.flags = 0; |
| + emad->comp_mrd_offset = comp_mrd_offset; |
| + emad->reserved_8_15 = 0; |
| + } |
| + comp_mrd->total_page_count = len / PAGE_SIZE; |
| + comp_mrd->address_range_count = nents; |
| + comp_mrd->reserved_8_15 = 0; |
| + |
| + total_len = cons_mrd_offset + nents * sizeof(*cons_mrd); |
| + sg = sglist; |
| + while (count) { |
| + size_t lcount = |
| + min_t(size_t, count, (PAGE_SIZE - cons_mrd_offset) / |
| + sizeof(*cons_mrd)); |
| + size_t fragment_len = lcount * sizeof(*cons_mrd) + |
| + cons_mrd_offset; |
| + |
| + for (i = 0; i < lcount; i++) { |
| + cons_mrd[i].address = sg_dma_address(sg); |
| + cons_mrd[i].page_count = sg_dma_len(sg) / PAGE_SIZE; |
| + cons_mrd[i].reserved_12_15 = 0; |
| + sg = sg_next(sg); |
| + } |
| + count -= lcount; |
| + if (cons_mrd_offset) { |
| + u32 smc = lend ? SMC_FC_FFA_MEM_LEND : |
| + SMC_FC_FFA_MEM_SHARE; |
| + /* First fragment */ |
| + smc_ret = trusty_smc8(smc, total_len, |
| + fragment_len, 0, 0, 0, 0, 0); |
| + } else { |
| + smc_ret = trusty_smc8(SMC_FC_FFA_MEM_FRAG_TX, |
| + cookie_low, cookie_high, |
| + fragment_len, 0, 0, 0, 0); |
| + } |
| + if (smc_ret.r0 == SMC_FC_FFA_MEM_FRAG_RX) { |
| + cookie_low = smc_ret.r1; |
| + cookie_high = smc_ret.r2; |
| + dev_dbg(s->dev, "cookie %x %x", cookie_low, |
| + cookie_high); |
| + if (!count) { |
| + /* |
| + * We have sent all our descriptors. Expected |
| + * SMC_FC_FFA_SUCCESS, not a request to send |
| + * another fragment. |
| + */ |
| + dev_err(s->dev, "%s: fragment_len %zd/%zd, unexpected SMC_FC_FFA_MEM_FRAG_RX\n", |
| + __func__, fragment_len, total_len); |
| + ret = -EIO; |
| + break; |
| + } |
| + } else if (smc_ret.r0 == SMC_FC_FFA_SUCCESS) { |
| + ffa_handle = smc_ret.r2 | (u64)smc_ret.r3 << 32; |
| + dev_dbg(s->dev, "%s: fragment_len %zu/%zu, got handle 0x%llx\n", |
| + __func__, fragment_len, total_len, |
| + ffa_handle); |
| + if (count) { |
| + /* |
| + * We have not sent all our descriptors. |
| + * Expected SMC_FC_FFA_MEM_FRAG_RX not |
| + * SMC_FC_FFA_SUCCESS. |
| + */ |
| + dev_err(s->dev, "%s: fragment_len %zu/%zu, unexpected SMC_FC_FFA_SUCCESS, count %zu != 0\n", |
| + __func__, fragment_len, total_len, |
| + count); |
| + ret = -EIO; |
| + break; |
| + } |
| + } else { |
| + dev_err(s->dev, "%s: fragment_len %zu/%zu, SMC_FC_FFA_MEM_SHARE failed 0x%lx 0x%lx 0x%lx", |
| + __func__, fragment_len, total_len, |
| + smc_ret.r0, smc_ret.r1, smc_ret.r2); |
| + ret = -EIO; |
| + break; |
| + } |
| + |
| + cons_mrd = s->ffa_tx; |
| + cons_mrd_offset = 0; |
| + } |
| + |
| + mutex_unlock(&s->share_memory_msg_lock); |
| + |
| + if (!ret) { |
| + *id = ffa_handle; |
| + dev_dbg(s->dev, "%s: done\n", __func__); |
| + return 0; |
| + } |
| + |
| + dev_err(s->dev, "%s: failed %d", __func__, ret); |
| + |
| +err_encode_page_info: |
| + dma_unmap_sg(dev, sglist, nents, DMA_BIDIRECTIONAL); |
| + return ret; |
| +} |
| +EXPORT_SYMBOL(trusty_transfer_memory); |
| + |
| +/* |
| + * trusty_share_memory_compat - trusty_share_memory wrapper for old apis |
| + * |
| + * Call trusty_share_memory and filter out memory attributes if trusty version |
| + * is old. Used by clients that used to pass just a physical address to trusty |
| + * instead of a physical address plus memory attributes value. |
| + */ |
| +int trusty_share_memory_compat(struct device *dev, u64 *id, |
| + struct scatterlist *sglist, unsigned int nents, |
| + pgprot_t pgprot) |
| +{ |
| + int ret; |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + ret = trusty_share_memory(dev, id, sglist, nents, pgprot); |
| + if (!ret && s->api_version < TRUSTY_API_VERSION_PHYS_MEM_OBJ) |
| + *id &= 0x0000FFFFFFFFF000ull; |
| + |
| + return ret; |
| +} |
| +EXPORT_SYMBOL(trusty_share_memory_compat); |
| + |
| +int trusty_reclaim_memory(struct device *dev, u64 id, |
| + struct scatterlist *sglist, unsigned int nents) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + int ret = 0; |
| + struct smc_ret8 smc_ret; |
| + |
| + if (WARN_ON(dev->driver != &trusty_driver.driver)) |
| + return -EINVAL; |
| + |
| + if (WARN_ON(nents < 1)) |
| + return -EINVAL; |
| + |
| + if (s->api_version < TRUSTY_API_VERSION_MEM_OBJ) { |
| + if (nents != 1) { |
| + dev_err(s->dev, "%s: not supported\n", __func__); |
| + return -EOPNOTSUPP; |
| + } |
| + |
| + dma_unmap_sg(dev, sglist, nents, DMA_BIDIRECTIONAL); |
| + |
| + dev_dbg(s->dev, "%s: done\n", __func__); |
| + return 0; |
| + } |
| + |
| + mutex_lock(&s->share_memory_msg_lock); |
| + |
| + smc_ret = trusty_smc8(SMC_FC_FFA_MEM_RECLAIM, (u32)id, id >> 32, 0, 0, |
| + 0, 0, 0); |
| + if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) { |
| + dev_err(s->dev, "%s: SMC_FC_FFA_MEM_RECLAIM failed 0x%lx 0x%lx 0x%lx", |
| + __func__, smc_ret.r0, smc_ret.r1, smc_ret.r2); |
| + if (smc_ret.r0 == SMC_FC_FFA_ERROR && |
| + smc_ret.r2 == FFA_ERROR_DENIED) |
| + ret = -EBUSY; |
| + else |
| + ret = -EIO; |
| + } |
| + |
| + mutex_unlock(&s->share_memory_msg_lock); |
| + |
| + if (ret != 0) |
| + return ret; |
| + |
| + dma_unmap_sg(dev, sglist, nents, DMA_BIDIRECTIONAL); |
| + |
| + dev_dbg(s->dev, "%s: done\n", __func__); |
| + return 0; |
| +} |
| +EXPORT_SYMBOL(trusty_reclaim_memory); |
| + |
| +int trusty_call_notifier_register(struct device *dev, struct notifier_block *n) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + return atomic_notifier_chain_register(&s->notifier, n); |
| +} |
| +EXPORT_SYMBOL(trusty_call_notifier_register); |
| + |
| +int trusty_call_notifier_unregister(struct device *dev, |
| + struct notifier_block *n) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + return atomic_notifier_chain_unregister(&s->notifier, n); |
| +} |
| +EXPORT_SYMBOL(trusty_call_notifier_unregister); |
| + |
| +static int trusty_remove_child(struct device *dev, void *data) |
| +{ |
| + platform_device_unregister(to_platform_device(dev)); |
| + return 0; |
| +} |
| + |
| +static ssize_t trusty_version_show(struct device *dev, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + return scnprintf(buf, PAGE_SIZE, "%s\n", s->version_str ?: "unknown"); |
| +} |
| + |
| +static DEVICE_ATTR(trusty_version, 0400, trusty_version_show, NULL); |
| + |
| +static struct attribute *trusty_attrs[] = { |
| + &dev_attr_trusty_version.attr, |
| + NULL, |
| +}; |
| +ATTRIBUTE_GROUPS(trusty); |
| + |
| +const char *trusty_version_str_get(struct device *dev) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + return s->version_str; |
| +} |
| +EXPORT_SYMBOL(trusty_version_str_get); |
| + |
| +static int trusty_init_msg_buf(struct trusty_state *s, struct device *dev) |
| +{ |
| + phys_addr_t tx_paddr; |
| + phys_addr_t rx_paddr; |
| + int ret; |
| + struct smc_ret8 smc_ret; |
| + |
| + if (s->api_version < TRUSTY_API_VERSION_MEM_OBJ) |
| + return 0; |
| + |
| + /* Get supported FF-A version and check if it is compatible */ |
| + smc_ret = trusty_smc8(SMC_FC_FFA_VERSION, FFA_CURRENT_VERSION, 0, 0, |
| + 0, 0, 0, 0); |
| + if (FFA_VERSION_TO_MAJOR(smc_ret.r0) != FFA_CURRENT_VERSION_MAJOR) { |
| + dev_err(s->dev, |
| + "%s: Unsupported FF-A version 0x%lx, expected 0x%x\n", |
| + __func__, smc_ret.r0, FFA_CURRENT_VERSION); |
| + ret = -EIO; |
| + goto err_version; |
| + } |
| + |
| + /* Check that SMC_FC_FFA_MEM_SHARE is implemented */ |
| + smc_ret = trusty_smc8(SMC_FC_FFA_FEATURES, SMC_FC_FFA_MEM_SHARE, 0, 0, |
| + 0, 0, 0, 0); |
| + if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) { |
| + dev_err(s->dev, |
| + "%s: SMC_FC_FFA_FEATURES(SMC_FC_FFA_MEM_SHARE) failed 0x%lx 0x%lx 0x%lx\n", |
| + __func__, smc_ret.r0, smc_ret.r1, smc_ret.r2); |
| + ret = -EIO; |
| + goto err_features; |
| + } |
| + |
| + /* |
| + * Set FF-A endpoint IDs. |
| + * |
| + * Hardcode 0x8000 for the secure os. |
| + * TODO: Use FF-A call or device tree to configure this dynamically |
| + */ |
| + smc_ret = trusty_smc8(SMC_FC_FFA_ID_GET, 0, 0, 0, 0, 0, 0, 0); |
| + if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) { |
| + dev_err(s->dev, |
| + "%s: SMC_FC_FFA_ID_GET failed 0x%lx 0x%lx 0x%lx\n", |
| + __func__, smc_ret.r0, smc_ret.r1, smc_ret.r2); |
| + ret = -EIO; |
| + goto err_id_get; |
| + } |
| + |
| + s->ffa_local_id = smc_ret.r2; |
| + s->ffa_remote_id = 0x8000; |
| + |
| + s->ffa_tx = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| + if (!s->ffa_tx) { |
| + ret = -ENOMEM; |
| + goto err_alloc_tx; |
| + } |
| + tx_paddr = virt_to_phys(s->ffa_tx); |
| + if (WARN_ON(tx_paddr & (PAGE_SIZE - 1))) { |
| + ret = -EINVAL; |
| + goto err_unaligned_tx_buf; |
| + } |
| + |
| + s->ffa_rx = kmalloc(PAGE_SIZE, GFP_KERNEL); |
| + if (!s->ffa_rx) { |
| + ret = -ENOMEM; |
| + goto err_alloc_rx; |
| + } |
| + rx_paddr = virt_to_phys(s->ffa_rx); |
| + if (WARN_ON(rx_paddr & (PAGE_SIZE - 1))) { |
| + ret = -EINVAL; |
| + goto err_unaligned_rx_buf; |
| + } |
| + |
| + smc_ret = trusty_smc8(SMC_FCZ_FFA_RXTX_MAP, tx_paddr, rx_paddr, 1, 0, |
| + 0, 0, 0); |
| + if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) { |
| + dev_err(s->dev, "%s: SMC_FCZ_FFA_RXTX_MAP failed 0x%lx 0x%lx 0x%lx\n", |
| + __func__, smc_ret.r0, smc_ret.r1, smc_ret.r2); |
| + ret = -EIO; |
| + goto err_rxtx_map; |
| + } |
| + |
| + return 0; |
| + |
| +err_rxtx_map: |
| +err_unaligned_rx_buf: |
| + kfree(s->ffa_rx); |
| + s->ffa_rx = NULL; |
| +err_alloc_rx: |
| +err_unaligned_tx_buf: |
| + kfree(s->ffa_tx); |
| + s->ffa_tx = NULL; |
| +err_alloc_tx: |
| +err_id_get: |
| +err_features: |
| +err_version: |
| + return ret; |
| +} |
| + |
| +static void trusty_free_msg_buf(struct trusty_state *s, struct device *dev) |
| +{ |
| + struct smc_ret8 smc_ret; |
| + |
| + smc_ret = trusty_smc8(SMC_FC_FFA_RXTX_UNMAP, 0, 0, 0, 0, 0, 0, 0); |
| + if (smc_ret.r0 != SMC_FC_FFA_SUCCESS) { |
| + dev_err(s->dev, "%s: SMC_FC_FFA_RXTX_UNMAP failed 0x%lx 0x%lx 0x%lx\n", |
| + __func__, smc_ret.r0, smc_ret.r1, smc_ret.r2); |
| + } else { |
| + kfree(s->ffa_rx); |
| + kfree(s->ffa_tx); |
| + } |
| +} |
| + |
| +static void trusty_init_version(struct trusty_state *s, struct device *dev) |
| +{ |
| + int ret; |
| + int i; |
| + int version_str_len; |
| + |
| + ret = trusty_fast_call32(dev, SMC_FC_GET_VERSION_STR, -1, 0, 0); |
| + if (ret <= 0) |
| + goto err_get_size; |
| + |
| + version_str_len = ret; |
| + |
| + s->version_str = kmalloc(version_str_len + 1, GFP_KERNEL); |
| + for (i = 0; i < version_str_len; i++) { |
| + ret = trusty_fast_call32(dev, SMC_FC_GET_VERSION_STR, i, 0, 0); |
| + if (ret < 0) |
| + goto err_get_char; |
| + s->version_str[i] = ret; |
| + } |
| + s->version_str[i] = '\0'; |
| + |
| + dev_info(dev, "trusty version: %s\n", s->version_str); |
| + return; |
| + |
| +err_get_char: |
| + kfree(s->version_str); |
| + s->version_str = NULL; |
| +err_get_size: |
| + dev_err(dev, "failed to get version: %d\n", ret); |
| +} |
| + |
| +u32 trusty_get_api_version(struct device *dev) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + return s->api_version; |
| +} |
| +EXPORT_SYMBOL(trusty_get_api_version); |
| + |
| +bool trusty_get_panic_status(struct device *dev) |
| +{ |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + if (WARN_ON(dev->driver != &trusty_driver.driver)) |
| + return false; |
| + return s->trusty_panicked; |
| +} |
| +EXPORT_SYMBOL(trusty_get_panic_status); |
| + |
| +static int trusty_init_api_version(struct trusty_state *s, struct device *dev) |
| +{ |
| + u32 api_version; |
| + |
| + api_version = trusty_fast_call32(dev, SMC_FC_API_VERSION, |
| + TRUSTY_API_VERSION_CURRENT, 0, 0); |
| + if (api_version == SM_ERR_UNDEFINED_SMC) |
| + api_version = 0; |
| + |
| + if (api_version > TRUSTY_API_VERSION_CURRENT) { |
| + dev_err(dev, "unsupported api version %u > %u\n", |
| + api_version, TRUSTY_API_VERSION_CURRENT); |
| + return -EINVAL; |
| + } |
| + |
| + dev_info(dev, "selected api version: %u (requested %u)\n", |
| + api_version, TRUSTY_API_VERSION_CURRENT); |
| + s->api_version = api_version; |
| + |
| + return 0; |
| +} |
| + |
| +static bool dequeue_nop(struct trusty_state *s, u32 *args) |
| +{ |
| + unsigned long flags; |
| + struct trusty_nop *nop = NULL; |
| + |
| + spin_lock_irqsave(&s->nop_lock, flags); |
| + if (!list_empty(&s->nop_queue)) { |
| + nop = list_first_entry(&s->nop_queue, |
| + struct trusty_nop, node); |
| + list_del_init(&nop->node); |
| + args[0] = nop->args[0]; |
| + args[1] = nop->args[1]; |
| + args[2] = nop->args[2]; |
| + } else { |
| + args[0] = 0; |
| + args[1] = 0; |
| + args[2] = 0; |
| + } |
| + spin_unlock_irqrestore(&s->nop_lock, flags); |
| + return nop; |
| +} |
| + |
| +static void locked_nop_work_func(struct work_struct *work) |
| +{ |
| + int ret; |
| + struct trusty_work *tw = container_of(work, struct trusty_work, work); |
| + struct trusty_state *s = tw->ts; |
| + |
| + ret = trusty_std_call32(s->dev, SMC_SC_LOCKED_NOP, 0, 0, 0); |
| + if (ret != 0) |
| + dev_err(s->dev, "%s: SMC_SC_LOCKED_NOP failed %d", |
| + __func__, ret); |
| + |
| + dev_dbg(s->dev, "%s: done\n", __func__); |
| +} |
| + |
| +static void nop_work_func(struct work_struct *work) |
| +{ |
| + int ret; |
| + bool next; |
| + u32 args[3]; |
| + u32 last_arg0; |
| + struct trusty_work *tw = container_of(work, struct trusty_work, work); |
| + struct trusty_state *s = tw->ts; |
| + |
| + dequeue_nop(s, args); |
| + do { |
| + dev_dbg(s->dev, "%s: %x %x %x\n", |
| + __func__, args[0], args[1], args[2]); |
| + |
| + last_arg0 = args[0]; |
| + ret = trusty_std_call32(s->dev, SMC_SC_NOP, |
| + args[0], args[1], args[2]); |
| + |
| + next = dequeue_nop(s, args); |
| + |
| + if (ret == SM_ERR_NOP_INTERRUPTED) { |
| + next = true; |
| + } else if (ret != SM_ERR_NOP_DONE) { |
| + dev_err(s->dev, "%s: SMC_SC_NOP %x failed %d", |
| + __func__, last_arg0, ret); |
| + if (last_arg0) { |
| + /* |
| + * Don't break out of the loop if a non-default |
| + * nop-handler returns an error. |
| + */ |
| + next = true; |
| + } |
| + } |
| + } while (next); |
| + |
| + dev_dbg(s->dev, "%s: done\n", __func__); |
| +} |
| + |
| +void trusty_enqueue_nop(struct device *dev, struct trusty_nop *nop) |
| +{ |
| + unsigned long flags; |
| + struct trusty_work *tw; |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + preempt_disable(); |
| + tw = this_cpu_ptr(s->nop_works); |
| + if (nop) { |
| + WARN_ON(s->api_version < TRUSTY_API_VERSION_SMP_NOP); |
| + |
| + spin_lock_irqsave(&s->nop_lock, flags); |
| + if (list_empty(&nop->node)) |
| + list_add_tail(&nop->node, &s->nop_queue); |
| + spin_unlock_irqrestore(&s->nop_lock, flags); |
| + } |
| + queue_work(s->nop_wq, &tw->work); |
| + preempt_enable(); |
| +} |
| +EXPORT_SYMBOL(trusty_enqueue_nop); |
| + |
| +void trusty_dequeue_nop(struct device *dev, struct trusty_nop *nop) |
| +{ |
| + unsigned long flags; |
| + struct trusty_state *s = platform_get_drvdata(to_platform_device(dev)); |
| + |
| + if (WARN_ON(!nop)) |
| + return; |
| + |
| + spin_lock_irqsave(&s->nop_lock, flags); |
| + if (!list_empty(&nop->node)) |
| + list_del_init(&nop->node); |
| + spin_unlock_irqrestore(&s->nop_lock, flags); |
| +} |
| +EXPORT_SYMBOL(trusty_dequeue_nop); |
| + |
| +static int trusty_probe(struct platform_device *pdev) |
| +{ |
| + int ret; |
| + unsigned int cpu; |
| + work_func_t work_func; |
| + struct trusty_state *s; |
| + struct device_node *node = pdev->dev.of_node; |
| + |
| + if (!node) { |
| + dev_err(&pdev->dev, "of_node required\n"); |
| + return -EINVAL; |
| + } |
| + |
| + s = kzalloc(sizeof(*s), GFP_KERNEL); |
| + if (!s) { |
| + ret = -ENOMEM; |
| + goto err_allocate_state; |
| + } |
| + |
| + s->dev = &pdev->dev; |
| + spin_lock_init(&s->nop_lock); |
| + INIT_LIST_HEAD(&s->nop_queue); |
| + mutex_init(&s->smc_lock); |
| + mutex_init(&s->share_memory_msg_lock); |
| + ATOMIC_INIT_NOTIFIER_HEAD(&s->notifier); |
| + init_completion(&s->cpu_idle_completion); |
| + |
| + s->dev->dma_parms = &s->dma_parms; |
| + dma_set_max_seg_size(s->dev, 0xfffff000); /* dma_parms limit */ |
| + /* |
| + * Set dma mask to 48 bits. This is the current limit of |
| + * trusty_encode_page_info. |
| + */ |
| + dma_coerce_mask_and_coherent(s->dev, DMA_BIT_MASK(48)); |
| + |
| + platform_set_drvdata(pdev, s); |
| + |
| + trusty_init_version(s, &pdev->dev); |
| + |
| + ret = trusty_init_api_version(s, &pdev->dev); |
| + if (ret < 0) |
| + goto err_api_version; |
| + |
| + ret = trusty_init_msg_buf(s, &pdev->dev); |
| + if (ret < 0) |
| + goto err_init_msg_buf; |
| + |
| + s->nop_wq = alloc_workqueue("trusty-nop-wq", WQ_CPU_INTENSIVE, 0); |
| + if (!s->nop_wq) { |
| + ret = -ENODEV; |
| + dev_err(&pdev->dev, "Failed create trusty-nop-wq\n"); |
| + goto err_create_nop_wq; |
| + } |
| + |
| + s->nop_works = alloc_percpu(struct trusty_work); |
| + if (!s->nop_works) { |
| + ret = -ENOMEM; |
| + dev_err(&pdev->dev, "Failed to allocate works\n"); |
| + goto err_alloc_works; |
| + } |
| + |
| + if (s->api_version < TRUSTY_API_VERSION_SMP) |
| + work_func = locked_nop_work_func; |
| + else |
| + work_func = nop_work_func; |
| + |
| + for_each_possible_cpu(cpu) { |
| + struct trusty_work *tw = per_cpu_ptr(s->nop_works, cpu); |
| + |
| + tw->ts = s; |
| + INIT_WORK(&tw->work, work_func); |
| + } |
| + |
| + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); |
| + if (ret < 0) { |
| + dev_err(&pdev->dev, "Failed to add children: %d\n", ret); |
| + goto err_add_children; |
| + } |
| + |
| + return 0; |
| + |
| +err_add_children: |
| + for_each_possible_cpu(cpu) { |
| + struct trusty_work *tw = per_cpu_ptr(s->nop_works, cpu); |
| + |
| + flush_work(&tw->work); |
| + } |
| + free_percpu(s->nop_works); |
| +err_alloc_works: |
| + destroy_workqueue(s->nop_wq); |
| +err_create_nop_wq: |
| + trusty_free_msg_buf(s, &pdev->dev); |
| +err_init_msg_buf: |
| +err_api_version: |
| + s->dev->dma_parms = NULL; |
| + kfree(s->version_str); |
| + device_for_each_child(&pdev->dev, NULL, trusty_remove_child); |
| + mutex_destroy(&s->share_memory_msg_lock); |
| + mutex_destroy(&s->smc_lock); |
| + kfree(s); |
| +err_allocate_state: |
| + return ret; |
| +} |
| + |
| +static int trusty_remove(struct platform_device *pdev) |
| +{ |
| + unsigned int cpu; |
| + struct trusty_state *s = platform_get_drvdata(pdev); |
| + |
| + device_for_each_child(&pdev->dev, NULL, trusty_remove_child); |
| + |
| + for_each_possible_cpu(cpu) { |
| + struct trusty_work *tw = per_cpu_ptr(s->nop_works, cpu); |
| + |
| + flush_work(&tw->work); |
| + } |
| + free_percpu(s->nop_works); |
| + destroy_workqueue(s->nop_wq); |
| + |
| + mutex_destroy(&s->share_memory_msg_lock); |
| + mutex_destroy(&s->smc_lock); |
| + trusty_free_msg_buf(s, &pdev->dev); |
| + s->dev->dma_parms = NULL; |
| + kfree(s->version_str); |
| + kfree(s); |
| + return 0; |
| +} |
| + |
| +static const struct of_device_id trusty_of_match[] = { |
| + { .compatible = "android,trusty-smc-v1", }, |
| + {}, |
| +}; |
| + |
| +MODULE_DEVICE_TABLE(trusty, trusty_of_match); |
| + |
| +static struct platform_driver trusty_driver = { |
| + .probe = trusty_probe, |
| + .remove = trusty_remove, |
| + .driver = { |
| + .name = "trusty", |
| + .of_match_table = trusty_of_match, |
| + .dev_groups = trusty_groups, |
| + }, |
| +}; |
| + |
| +static int __init trusty_driver_init(void) |
| +{ |
| + return platform_driver_register(&trusty_driver); |
| +} |
| + |
| +static void __exit trusty_driver_exit(void) |
| +{ |
| + platform_driver_unregister(&trusty_driver); |
| +} |
| + |
| +subsys_initcall(trusty_driver_init); |
| +module_exit(trusty_driver_exit); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_DESCRIPTION("Trusty core driver"); |
| diff --git a/include/linux/trusty/arm_ffa.h b/include/linux/trusty/arm_ffa.h |
| new file mode 100644 |
| index 000000000000..ab7b2afb794c |
| --- /dev/null |
| +++ b/include/linux/trusty/arm_ffa.h |
| @@ -0,0 +1,590 @@ |
| +/* SPDX-License-Identifier: MIT */ |
| +/* |
| + * Copyright (C) 2020 Google, Inc. |
| + * |
| + * Trusty and TF-A also have a copy of this header. |
| + * Please keep the copies in sync. |
| + */ |
| +#ifndef __LINUX_TRUSTY_ARM_FFA_H |
| +#define __LINUX_TRUSTY_ARM_FFA_H |
| + |
| +/* |
| + * Subset of Arm PSA Firmware Framework for Arm v8-A 1.0 EAC 1_0 |
| + * (https://developer.arm.com/docs/den0077/a) needed for shared memory. |
| + */ |
| + |
| +#include "smcall.h" |
| + |
| +#ifndef STATIC_ASSERT |
| +#define STATIC_ASSERT(e) _Static_assert(e, #e) |
| +#endif |
| + |
| +#define FFA_CURRENT_VERSION_MAJOR (1U) |
| +#define FFA_CURRENT_VERSION_MINOR (0U) |
| + |
| +#define FFA_VERSION_TO_MAJOR(version) ((version) >> 16) |
| +#define FFA_VERSION_TO_MINOR(version) ((version) & (0xffff)) |
| +#define FFA_VERSION(major, minor) (((major) << 16) | (minor)) |
| +#define FFA_CURRENT_VERSION \ |
| + FFA_VERSION(FFA_CURRENT_VERSION_MAJOR, FFA_CURRENT_VERSION_MINOR) |
| + |
| +#define SMC_ENTITY_SHARED_MEMORY 4 |
| + |
| +#define SMC_FASTCALL_NR_SHARED_MEMORY(nr) \ |
| + SMC_FASTCALL_NR(SMC_ENTITY_SHARED_MEMORY, nr) |
| +#define SMC_FASTCALL64_NR_SHARED_MEMORY(nr) \ |
| + SMC_FASTCALL64_NR(SMC_ENTITY_SHARED_MEMORY, nr) |
| + |
| +/** |
| + * typedef ffa_endpoint_id16_t - Endpoint ID |
| + * |
| + * Current implementation only supports VMIDs. FFA spec also support stream |
| + * endpoint ids. |
| + */ |
| +typedef uint16_t ffa_endpoint_id16_t; |
| + |
| +/** |
| + * struct ffa_cons_mrd - Constituent memory region descriptor |
| + * @address: |
| + * Start address of contiguous memory region. Must be 4K page aligned. |
| + * @page_count: |
| + * Number of 4K pages in region. |
| + * @reserved_12_15: |
| + * Reserve bytes 12-15 to pad struct size to 16 bytes. |
| + */ |
| +struct ffa_cons_mrd { |
| + uint64_t address; |
| + uint32_t page_count; |
| + uint32_t reserved_12_15; |
| +}; |
| +STATIC_ASSERT(sizeof(struct ffa_cons_mrd) == 16); |
| + |
| +/** |
| + * struct ffa_comp_mrd - Composite memory region descriptor |
| + * @total_page_count: |
| + * Number of 4k pages in memory region. Must match sum of |
| + * @address_range_array[].page_count. |
| + * @address_range_count: |
| + * Number of entries in @address_range_array. |
| + * @reserved_8_15: |
| + * Reserve bytes 8-15 to pad struct size to 16 byte alignment and |
| + * make @address_range_array 16 byte aligned. |
| + * @address_range_array: |
| + * Array of &struct ffa_cons_mrd entries. |
| + */ |
| +struct ffa_comp_mrd { |
| + uint32_t total_page_count; |
| + uint32_t address_range_count; |
| + uint64_t reserved_8_15; |
| + struct ffa_cons_mrd address_range_array[]; |
| +}; |
| +STATIC_ASSERT(sizeof(struct ffa_comp_mrd) == 16); |
| + |
| +/** |
| + * typedef ffa_mem_attr8_t - Memory region attributes |
| + * |
| + * * @FFA_MEM_ATTR_DEVICE_NGNRNE: |
| + * Device-nGnRnE. |
| + * * @FFA_MEM_ATTR_DEVICE_NGNRE: |
| + * Device-nGnRE. |
| + * * @FFA_MEM_ATTR_DEVICE_NGRE: |
| + * Device-nGRE. |
| + * * @FFA_MEM_ATTR_DEVICE_GRE: |
| + * Device-GRE. |
| + * * @FFA_MEM_ATTR_NORMAL_MEMORY_UNCACHED |
| + * Normal memory. Non-cacheable. |
| + * * @FFA_MEM_ATTR_NORMAL_MEMORY_CACHED_WB |
| + * Normal memory. Write-back cached. |
| + * * @FFA_MEM_ATTR_NON_SHAREABLE |
| + * Non-shareable. Combine with FFA_MEM_ATTR_NORMAL_MEMORY_*. |
| + * * @FFA_MEM_ATTR_OUTER_SHAREABLE |
| + * Outer Shareable. Combine with FFA_MEM_ATTR_NORMAL_MEMORY_*. |
| + * * @FFA_MEM_ATTR_INNER_SHAREABLE |
| + * Inner Shareable. Combine with FFA_MEM_ATTR_NORMAL_MEMORY_*. |
| + */ |
| +typedef uint8_t ffa_mem_attr8_t; |
| +#define FFA_MEM_ATTR_DEVICE_NGNRNE ((1U << 4) | (0x0U << 2)) |
| +#define FFA_MEM_ATTR_DEVICE_NGNRE ((1U << 4) | (0x1U << 2)) |
| +#define FFA_MEM_ATTR_DEVICE_NGRE ((1U << 4) | (0x2U << 2)) |
| +#define FFA_MEM_ATTR_DEVICE_GRE ((1U << 4) | (0x3U << 2)) |
| +#define FFA_MEM_ATTR_NORMAL_MEMORY_UNCACHED ((2U << 4) | (0x1U << 2)) |
| +#define FFA_MEM_ATTR_NORMAL_MEMORY_CACHED_WB ((2U << 4) | (0x3U << 2)) |
| +#define FFA_MEM_ATTR_NON_SHAREABLE (0x0U << 0) |
| +#define FFA_MEM_ATTR_OUTER_SHAREABLE (0x2U << 0) |
| +#define FFA_MEM_ATTR_INNER_SHAREABLE (0x3U << 0) |
| + |
| +/** |
| + * typedef ffa_mem_perm8_t - Memory access permissions |
| + * |
| + * * @FFA_MEM_ATTR_RO |
| + * Request or specify read-only mapping. |
| + * * @FFA_MEM_ATTR_RW |
| + * Request or allow read-write mapping. |
| + * * @FFA_MEM_PERM_NX |
| + * Deny executable mapping. |
| + * * @FFA_MEM_PERM_X |
| + * Request executable mapping. |
| + */ |
| +typedef uint8_t ffa_mem_perm8_t; |
| +#define FFA_MEM_PERM_RO (1U << 0) |
| +#define FFA_MEM_PERM_RW (1U << 1) |
| +#define FFA_MEM_PERM_NX (1U << 2) |
| +#define FFA_MEM_PERM_X (1U << 3) |
| + |
| +/** |
| + * typedef ffa_mem_flag8_t - Endpoint memory flags |
| + * |
| + * * @FFA_MEM_FLAG_OTHER |
| + * Other borrower. Memory region must not be or was not retrieved on behalf |
| + * of this endpoint. |
| + */ |
| +typedef uint8_t ffa_mem_flag8_t; |
| +#define FFA_MEM_FLAG_OTHER (1U << 0) |
| + |
| +/** |
| + * typedef ffa_mtd_flag32_t - Memory transaction descriptor flags |
| + * |
| + * * @FFA_MTD_FLAG_ZERO_MEMORY |
| + * Zero memory after unmapping from sender (must be 0 for share). |
| + * * @FFA_MTD_FLAG_TIME_SLICING |
| + * Not supported by this implementation. |
| + * * @FFA_MTD_FLAG_ZERO_MEMORY_AFTER_RELINQUISH |
| + * Zero memory after unmapping from borrowers (must be 0 for share). |
| + * * @FFA_MTD_FLAG_TYPE_MASK |
| + * Bit-mask to extract memory management transaction type from flags. |
| + * * @FFA_MTD_FLAG_TYPE_SHARE_MEMORY |
| + * Share memory transaction flag. |
| + * Used by @SMC_FC_FFA_MEM_RETRIEVE_RESP to indicate that memory came from |
| + * @SMC_FC_FFA_MEM_SHARE and by @SMC_FC_FFA_MEM_RETRIEVE_REQ to specify that |
| + * it must have. |
| + * * @FFA_MTD_FLAG_ADDRESS_RANGE_ALIGNMENT_HINT_MASK |
| + * Not supported by this implementation. |
| + */ |
| +typedef uint32_t ffa_mtd_flag32_t; |
| +#define FFA_MTD_FLAG_ZERO_MEMORY (1U << 0) |
| +#define FFA_MTD_FLAG_TIME_SLICING (1U << 1) |
| +#define FFA_MTD_FLAG_ZERO_MEMORY_AFTER_RELINQUISH (1U << 2) |
| +#define FFA_MTD_FLAG_TYPE_MASK (3U << 3) |
| +#define FFA_MTD_FLAG_TYPE_SHARE_MEMORY (1U << 3) |
| +#define FFA_MTD_FLAG_ADDRESS_RANGE_ALIGNMENT_HINT_MASK (0x1FU << 5) |
| + |
| +/** |
| + * struct ffa_mapd - Memory access permissions descriptor |
| + * @endpoint_id: |
| + * Endpoint id that @memory_access_permissions and @flags apply to. |
| + * (&typedef ffa_endpoint_id16_t). |
| + * @memory_access_permissions: |
| + * FFA_MEM_PERM_* values or'ed together (&typedef ffa_mem_perm8_t). |
| + * @flags: |
| + * FFA_MEM_FLAG_* values or'ed together (&typedef ffa_mem_flag8_t). |
| + */ |
| +struct ffa_mapd { |
| + ffa_endpoint_id16_t endpoint_id; |
| + ffa_mem_perm8_t memory_access_permissions; |
| + ffa_mem_flag8_t flags; |
| +}; |
| +STATIC_ASSERT(sizeof(struct ffa_mapd) == 4); |
| + |
| +/** |
| + * struct ffa_emad - Endpoint memory access descriptor. |
| + * @mapd: &struct ffa_mapd. |
| + * @comp_mrd_offset: |
| + * Offset of &struct ffa_comp_mrd form start of &struct ffa_mtd. |
| + * @reserved_8_15: |
| + * Reserved bytes 8-15. Must be 0. |
| + */ |
| +struct ffa_emad { |
| + struct ffa_mapd mapd; |
| + uint32_t comp_mrd_offset; |
| + uint64_t reserved_8_15; |
| +}; |
| +STATIC_ASSERT(sizeof(struct ffa_emad) == 16); |
| + |
| +/** |
| + * struct ffa_mtd - Memory transaction descriptor. |
| + * @sender_id: |
| + * Sender endpoint id. |
| + * @memory_region_attributes: |
| + * FFA_MEM_ATTR_* values or'ed together (&typedef ffa_mem_attr8_t). |
| + * @reserved_3: |
| + * Reserved bytes 3. Must be 0. |
| + * @flags: |
| + * FFA_MTD_FLAG_* values or'ed together (&typedef ffa_mtd_flag32_t). |
| + * @handle: |
| + * Id of shared memory object. Most be 0 for MEM_SHARE. |
| + * @tag: Client allocated tag. Must match original value. |
| + * @reserved_24_27: |
| + * Reserved bytes 24-27. Must be 0. |
| + * @emad_count: |
| + * Number of entries in @emad. Must be 1 in current implementation. |
| + * FFA spec allows more entries. |
| + * @emad: |
| + * Endpoint memory access descriptor array (see @struct ffa_emad). |
| + */ |
| +struct ffa_mtd { |
| + ffa_endpoint_id16_t sender_id; |
| + ffa_mem_attr8_t memory_region_attributes; |
| + uint8_t reserved_3; |
| + ffa_mtd_flag32_t flags; |
| + uint64_t handle; |
| + uint64_t tag; |
| + uint32_t reserved_24_27; |
| + uint32_t emad_count; |
| + struct ffa_emad emad[]; |
| +}; |
| +STATIC_ASSERT(sizeof(struct ffa_mtd) == 32); |
| + |
| +/** |
| + * struct ffa_mem_relinquish_descriptor - Relinquish request descriptor. |
| + * @handle: |
| + * Id of shared memory object to relinquish. |
| + * @flags: |
| + * If bit 0 is set clear memory after unmapping from borrower. Must be 0 |
| + * for share. Bit[1]: Time slicing. Not supported, must be 0. All other |
| + * bits are reserved 0. |
| + * @endpoint_count: |
| + * Number of entries in @endpoint_array. |
| + * @endpoint_array: |
| + * Array of endpoint ids. |
| + */ |
| +struct ffa_mem_relinquish_descriptor { |
| + uint64_t handle; |
| + uint32_t flags; |
| + uint32_t endpoint_count; |
| + ffa_endpoint_id16_t endpoint_array[]; |
| +}; |
| +STATIC_ASSERT(sizeof(struct ffa_mem_relinquish_descriptor) == 16); |
| + |
| +/** |
| + * enum ffa_error - FF-A error code |
| + * @FFA_ERROR_NOT_SUPPORTED: |
| + * Operation contained possibly valid parameters not supported by the |
| + * current implementation. Does not match FF-A 1.0 EAC 1_0 definition. |
| + * @FFA_ERROR_INVALID_PARAMETERS: |
| + * Invalid parameters. Conditions function specific. |
| + * @FFA_ERROR_NO_MEMORY: |
| + * Not enough memory. |
| + * @FFA_ERROR_DENIED: |
| + * Operation not allowed. Conditions function specific. |
| + * |
| + * FF-A 1.0 EAC 1_0 defines other error codes as well but the current |
| + * implementation does not use them. |
| + */ |
| +enum ffa_error { |
| + FFA_ERROR_NOT_SUPPORTED = -1, |
| + FFA_ERROR_INVALID_PARAMETERS = -2, |
| + FFA_ERROR_NO_MEMORY = -3, |
| + FFA_ERROR_DENIED = -6, |
| +}; |
| + |
| +/** |
| + * SMC_FC32_FFA_MIN - First 32 bit SMC opcode reserved for FFA |
| + */ |
| +#define SMC_FC32_FFA_MIN SMC_FASTCALL_NR_SHARED_MEMORY(0x60) |
| + |
| +/** |
| + * SMC_FC32_FFA_MAX - Last 32 bit SMC opcode reserved for FFA |
| + */ |
| +#define SMC_FC32_FFA_MAX SMC_FASTCALL_NR_SHARED_MEMORY(0x7F) |
| + |
| +/** |
| + * SMC_FC64_FFA_MIN - First 64 bit SMC opcode reserved for FFA |
| + */ |
| +#define SMC_FC64_FFA_MIN SMC_FASTCALL64_NR_SHARED_MEMORY(0x60) |
| + |
| +/** |
| + * SMC_FC64_FFA_MAX - Last 64 bit SMC opcode reserved for FFA |
| + */ |
| +#define SMC_FC64_FFA_MAX SMC_FASTCALL64_NR_SHARED_MEMORY(0x7F) |
| + |
| +/** |
| + * SMC_FC_FFA_ERROR - SMC error return opcode |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: VMID in [31:16], vCPU in [15:0] |
| + * * w2: Error code (&enum ffa_error) |
| + */ |
| +#define SMC_FC_FFA_ERROR SMC_FASTCALL_NR_SHARED_MEMORY(0x60) |
| + |
| +/** |
| + * SMC_FC_FFA_SUCCESS - 32 bit SMC success return opcode |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: VMID in [31:16], vCPU in [15:0] |
| + * * w2-w7: Function specific |
| + */ |
| +#define SMC_FC_FFA_SUCCESS SMC_FASTCALL_NR_SHARED_MEMORY(0x61) |
| + |
| +/** |
| + * SMC_FC64_FFA_SUCCESS - 64 bit SMC success return opcode |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: VMID in [31:16], vCPU in [15:0] |
| + * * w2/x2-w7/x7: Function specific |
| + */ |
| +#define SMC_FC64_FFA_SUCCESS SMC_FASTCALL64_NR_SHARED_MEMORY(0x61) |
| + |
| +/** |
| + * SMC_FC_FFA_VERSION - SMC opcode to return supported FF-A version |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: Major version bit[30:16] and minor version in bit[15:0] supported |
| + * by caller. Bit[31] must be 0. |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + * * w2: Major version bit[30:16], minor version in bit[15:0], bit[31] must |
| + * be 0. |
| + * |
| + * or |
| + * |
| + * * w0: SMC_FC_FFA_ERROR |
| + * * w2: FFA_ERROR_NOT_SUPPORTED if major version passed in is less than the |
| + * minimum major version supported. |
| + */ |
| +#define SMC_FC_FFA_VERSION SMC_FASTCALL_NR_SHARED_MEMORY(0x63) |
| + |
| +/** |
| + * SMC_FC_FFA_FEATURES - SMC opcode to check optional feature support |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: FF-A function ID |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + * * w2: Bit[0]: Supports custom buffers for memory transactions. |
| + * Bit[1:0]: For RXTX_MAP min buffer size and alignment boundary. |
| + * Other bits must be 0. |
| + * * w3: For FFA_MEM_RETRIEVE_REQ, bit[7-0]: Number of times receiver can |
| + * retrieve each memory region before relinquishing it specified as |
| + * ((1U << (value + 1)) - 1 (or value = bits in reference count - 1). |
| + * For all other bits and commands: must be 0. |
| + * or |
| + * |
| + * * w0: SMC_FC_FFA_ERROR |
| + * * w2: FFA_ERROR_NOT_SUPPORTED if function is not implemented, or |
| + * FFA_ERROR_INVALID_PARAMETERS if function id is not valid. |
| + */ |
| +#define SMC_FC_FFA_FEATURES SMC_FASTCALL_NR_SHARED_MEMORY(0x64) |
| + |
| +/** |
| + * SMC_FC_FFA_RXTX_MAP - 32 bit SMC opcode to map message buffers |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: TX address |
| + * * w2: RX address |
| + * * w3: RX/TX page count in bit[5:0] |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + */ |
| +#define SMC_FC_FFA_RXTX_MAP SMC_FASTCALL_NR_SHARED_MEMORY(0x66) |
| + |
| +/** |
| + * SMC_FC64_FFA_RXTX_MAP - 64 bit SMC opcode to map message buffers |
| + * |
| + * Register arguments: |
| + * |
| + * * x1: TX address |
| + * * x2: RX address |
| + * * x3: RX/TX page count in bit[5:0] |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + */ |
| +#define SMC_FC64_FFA_RXTX_MAP SMC_FASTCALL64_NR_SHARED_MEMORY(0x66) |
| +#ifdef CONFIG_64BIT |
| +#define SMC_FCZ_FFA_RXTX_MAP SMC_FC64_FFA_RXTX_MAP |
| +#else |
| +#define SMC_FCZ_FFA_RXTX_MAP SMC_FC_FFA_RXTX_MAP |
| +#endif |
| + |
| +/** |
| + * SMC_FC_FFA_RXTX_UNMAP - SMC opcode to unmap message buffers |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: ID in [31:16] |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + */ |
| +#define SMC_FC_FFA_RXTX_UNMAP SMC_FASTCALL_NR_SHARED_MEMORY(0x67) |
| + |
| +/** |
| + * SMC_FC_FFA_ID_GET - SMC opcode to get endpoint id of caller |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + * * w2: ID in bit[15:0], bit[31:16] must be 0. |
| + */ |
| +#define SMC_FC_FFA_ID_GET SMC_FASTCALL_NR_SHARED_MEMORY(0x69) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_DONATE - 32 bit SMC opcode to donate memory |
| + * |
| + * Not supported. |
| + */ |
| +#define SMC_FC_FFA_MEM_DONATE SMC_FASTCALL_NR_SHARED_MEMORY(0x71) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_LEND - 32 bit SMC opcode to lend memory |
| + * |
| + * Not currently supported. |
| + */ |
| +#define SMC_FC_FFA_MEM_LEND SMC_FASTCALL_NR_SHARED_MEMORY(0x72) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_SHARE - 32 bit SMC opcode to share memory |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: Total length |
| + * * w2: Fragment length |
| + * * w3: Address |
| + * * w4: Page count |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + * * w2/w3: Handle |
| + * |
| + * or |
| + * |
| + * * w0: &SMC_FC_FFA_MEM_FRAG_RX |
| + * * w1-: See &SMC_FC_FFA_MEM_FRAG_RX |
| + * |
| + * or |
| + * |
| + * * w0: SMC_FC_FFA_ERROR |
| + * * w2: Error code (&enum ffa_error) |
| + */ |
| +#define SMC_FC_FFA_MEM_SHARE SMC_FASTCALL_NR_SHARED_MEMORY(0x73) |
| + |
| +/** |
| + * SMC_FC64_FFA_MEM_SHARE - 64 bit SMC opcode to share memory |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: Total length |
| + * * w2: Fragment length |
| + * * x3: Address |
| + * * w4: Page count |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + * * w2/w3: Handle |
| + * |
| + * or |
| + * |
| + * * w0: &SMC_FC_FFA_MEM_FRAG_RX |
| + * * w1-: See &SMC_FC_FFA_MEM_FRAG_RX |
| + * |
| + * or |
| + * |
| + * * w0: SMC_FC_FFA_ERROR |
| + * * w2: Error code (&enum ffa_error) |
| + */ |
| +#define SMC_FC64_FFA_MEM_SHARE SMC_FASTCALL64_NR_SHARED_MEMORY(0x73) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_RETRIEVE_REQ - 32 bit SMC opcode to retrieve shared memory |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: Total length |
| + * * w2: Fragment length |
| + * * w3: Address |
| + * * w4: Page count |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_MEM_RETRIEVE_RESP |
| + * * w1/x1-w5/x5: See &SMC_FC_FFA_MEM_RETRIEVE_RESP |
| + */ |
| +#define SMC_FC_FFA_MEM_RETRIEVE_REQ SMC_FASTCALL_NR_SHARED_MEMORY(0x74) |
| + |
| +/** |
| + * SMC_FC64_FFA_MEM_RETRIEVE_REQ - 64 bit SMC opcode to retrieve shared memory |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: Total length |
| + * * w2: Fragment length |
| + * * x3: Address |
| + * * w4: Page count |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_MEM_RETRIEVE_RESP |
| + * * w1/x1-w5/x5: See &SMC_FC_FFA_MEM_RETRIEVE_RESP |
| + */ |
| +#define SMC_FC64_FFA_MEM_RETRIEVE_REQ SMC_FASTCALL64_NR_SHARED_MEMORY(0x74) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_RETRIEVE_RESP - Retrieve 32 bit SMC return opcode |
| + * |
| + * Register arguments: |
| + * |
| + * * w1: Total length |
| + * * w2: Fragment length |
| + */ |
| +#define SMC_FC_FFA_MEM_RETRIEVE_RESP SMC_FASTCALL_NR_SHARED_MEMORY(0x75) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_RELINQUISH - SMC opcode to relinquish shared memory |
| + * |
| + * Input in &struct ffa_mem_relinquish_descriptor format in message buffer. |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + */ |
| +#define SMC_FC_FFA_MEM_RELINQUISH SMC_FASTCALL_NR_SHARED_MEMORY(0x76) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_RECLAIM - SMC opcode to reclaim shared memory |
| + * |
| + * Register arguments: |
| + * |
| + * * w1/w2: Handle |
| + * * w3: Flags |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_SUCCESS |
| + */ |
| +#define SMC_FC_FFA_MEM_RECLAIM SMC_FASTCALL_NR_SHARED_MEMORY(0x77) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_FRAG_RX - SMC opcode to request next fragment. |
| + * |
| + * Register arguments: |
| + * |
| + * * w1/w2: Cookie |
| + * * w3: Fragment offset. |
| + * * w4: Endpoint id ID in [31:16], if client is hypervisor. |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_MEM_FRAG_TX |
| + * * w1/x1-w5/x5: See &SMC_FC_FFA_MEM_FRAG_TX |
| + */ |
| +#define SMC_FC_FFA_MEM_FRAG_RX SMC_FASTCALL_NR_SHARED_MEMORY(0x7A) |
| + |
| +/** |
| + * SMC_FC_FFA_MEM_FRAG_TX - SMC opcode to transmit next fragment |
| + * |
| + * Register arguments: |
| + * |
| + * * w1/w2: Cookie |
| + * * w3: Fragment length. |
| + * * w4: Sender endpoint id ID in [31:16], if client is hypervisor. |
| + * |
| + * Return: |
| + * * w0: &SMC_FC_FFA_MEM_FRAG_RX or &SMC_FC_FFA_SUCCESS. |
| + * * w1/x1-w5/x5: See opcode in w0. |
| + */ |
| +#define SMC_FC_FFA_MEM_FRAG_TX SMC_FASTCALL_NR_SHARED_MEMORY(0x7B) |
| + |
| +#endif /* __LINUX_TRUSTY_ARM_FFA_H */ |
| diff --git a/include/linux/trusty/sm_err.h b/include/linux/trusty/sm_err.h |
| new file mode 100644 |
| index 000000000000..f6504448c6c3 |
| --- /dev/null |
| +++ b/include/linux/trusty/sm_err.h |
| @@ -0,0 +1,28 @@ |
| +/* SPDX-License-Identifier: MIT */ |
| +/* |
| + * Copyright (c) 2013 Google Inc. All rights reserved |
| + * |
| + * Trusty and TF-A also have a copy of this header. |
| + * Please keep the copies in sync. |
| + */ |
| +#ifndef __LINUX_TRUSTY_SM_ERR_H |
| +#define __LINUX_TRUSTY_SM_ERR_H |
| + |
| +/* Errors from the secure monitor */ |
| +#define SM_ERR_UNDEFINED_SMC 0xFFFFFFFF /* Unknown SMC (defined by ARM DEN 0028A(0.9.0) */ |
| +#define SM_ERR_INVALID_PARAMETERS -2 |
| +#define SM_ERR_INTERRUPTED -3 /* Got interrupted. Call back with restart SMC */ |
| +#define SM_ERR_UNEXPECTED_RESTART -4 /* Got an restart SMC when we didn't expect it */ |
| +#define SM_ERR_BUSY -5 /* Temporarily busy. Call back with original args */ |
| +#define SM_ERR_INTERLEAVED_SMC -6 /* Got a trusted_service SMC when a restart SMC is required */ |
| +#define SM_ERR_INTERNAL_FAILURE -7 /* Unknown error */ |
| +#define SM_ERR_NOT_SUPPORTED -8 |
| +#define SM_ERR_NOT_ALLOWED -9 /* SMC call not allowed */ |
| +#define SM_ERR_END_OF_INPUT -10 |
| +#define SM_ERR_PANIC -11 /* Secure OS crashed */ |
| +#define SM_ERR_FIQ_INTERRUPTED -12 /* Got interrupted by FIQ. Call back with SMC_SC_RESTART_FIQ on same CPU */ |
| +#define SM_ERR_CPU_IDLE -13 /* SMC call waiting for another CPU */ |
| +#define SM_ERR_NOP_INTERRUPTED -14 /* Got interrupted. Call back with new SMC_SC_NOP */ |
| +#define SM_ERR_NOP_DONE -15 /* Cpu idle after SMC_SC_NOP (not an error) */ |
| + |
| +#endif |
| diff --git a/include/linux/trusty/smcall.h b/include/linux/trusty/smcall.h |
| new file mode 100644 |
| index 000000000000..aea3f6068593 |
| --- /dev/null |
| +++ b/include/linux/trusty/smcall.h |
| @@ -0,0 +1,124 @@ |
| +/* SPDX-License-Identifier: MIT */ |
| +/* |
| + * Copyright (c) 2013-2014 Google Inc. All rights reserved |
| + * |
| + * Trusty and TF-A also have a copy of this header. |
| + * Please keep the copies in sync. |
| + */ |
| +#ifndef __LINUX_TRUSTY_SMCALL_H |
| +#define __LINUX_TRUSTY_SMCALL_H |
| + |
| +#define SMC_NUM_ENTITIES 64 |
| +#define SMC_NUM_ARGS 4 |
| +#define SMC_NUM_PARAMS (SMC_NUM_ARGS - 1) |
| + |
| +#define SMC_IS_FASTCALL(smc_nr) ((smc_nr) & 0x80000000) |
| +#define SMC_IS_SMC64(smc_nr) ((smc_nr) & 0x40000000) |
| +#define SMC_ENTITY(smc_nr) (((smc_nr) & 0x3F000000) >> 24) |
| +#define SMC_FUNCTION(smc_nr) ((smc_nr) & 0x0000FFFF) |
| + |
| +#define SMC_NR(entity, fn, fastcall, smc64) ((((fastcall) & 0x1U) << 31) | \ |
| + (((smc64) & 0x1U) << 30) | \ |
| + (((entity) & 0x3FU) << 24) | \ |
| + ((fn) & 0xFFFFU) \ |
| + ) |
| + |
| +#define SMC_FASTCALL_NR(entity, fn) SMC_NR((entity), (fn), 1, 0) |
| +#define SMC_STDCALL_NR(entity, fn) SMC_NR((entity), (fn), 0, 0) |
| +#define SMC_FASTCALL64_NR(entity, fn) SMC_NR((entity), (fn), 1, 1) |
| +#define SMC_STDCALL64_NR(entity, fn) SMC_NR((entity), (fn), 0, 1) |
| + |
| +#define SMC_ENTITY_ARCH 0 /* ARM Architecture calls */ |
| +#define SMC_ENTITY_CPU 1 /* CPU Service calls */ |
| +#define SMC_ENTITY_SIP 2 /* SIP Service calls */ |
| +#define SMC_ENTITY_OEM 3 /* OEM Service calls */ |
| +#define SMC_ENTITY_STD 4 /* Standard Service calls */ |
| +#define SMC_ENTITY_RESERVED 5 /* Reserved for future use */ |
| +#define SMC_ENTITY_TRUSTED_APP 48 /* Trusted Application calls */ |
| +#define SMC_ENTITY_TRUSTED_OS 50 /* Trusted OS calls */ |
| +#define SMC_ENTITY_LOGGING 51 /* Used for secure -> nonsecure logging */ |
| +#define SMC_ENTITY_TEST 52 /* Used for secure -> nonsecure tests */ |
| +#define SMC_ENTITY_SECURE_MONITOR 60 /* Trusted OS calls internal to secure monitor */ |
| + |
| +/* FC = Fast call, SC = Standard call */ |
| +#define SMC_SC_RESTART_LAST SMC_STDCALL_NR(SMC_ENTITY_SECURE_MONITOR, 0) |
| +#define SMC_SC_LOCKED_NOP SMC_STDCALL_NR(SMC_ENTITY_SECURE_MONITOR, 1) |
| + |
| +/** |
| + * SMC_SC_RESTART_FIQ - Re-enter trusty after it was interrupted by an fiq |
| + * |
| + * No arguments, no return value. |
| + * |
| + * Re-enter trusty after returning to ns to process an fiq. Must be called iff |
| + * trusty returns SM_ERR_FIQ_INTERRUPTED. |
| + * |
| + * Enable by selecting api version TRUSTY_API_VERSION_RESTART_FIQ (1) or later. |
| + */ |
| +#define SMC_SC_RESTART_FIQ SMC_STDCALL_NR(SMC_ENTITY_SECURE_MONITOR, 2) |
| + |
| +/** |
| + * SMC_SC_NOP - Enter trusty to run pending work. |
| + * |
| + * No arguments. |
| + * |
| + * Returns SM_ERR_NOP_INTERRUPTED or SM_ERR_NOP_DONE. |
| + * If SM_ERR_NOP_INTERRUPTED is returned, the call must be repeated. |
| + * |
| + * Enable by selecting api version TRUSTY_API_VERSION_SMP (2) or later. |
| + */ |
| +#define SMC_SC_NOP SMC_STDCALL_NR(SMC_ENTITY_SECURE_MONITOR, 3) |
| + |
| +/* |
| + * Return from secure os to non-secure os with return value in r1 |
| + */ |
| +#define SMC_SC_NS_RETURN SMC_STDCALL_NR(SMC_ENTITY_SECURE_MONITOR, 0) |
| + |
| +#define SMC_FC_RESERVED SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 0) |
| +#define SMC_FC_FIQ_EXIT SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 1) |
| +#define SMC_FC_REQUEST_FIQ SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 2) |
| + |
| +#define TRUSTY_IRQ_TYPE_NORMAL (0) |
| +#define TRUSTY_IRQ_TYPE_PER_CPU (1) |
| +#define TRUSTY_IRQ_TYPE_DOORBELL (2) |
| +#define SMC_FC_GET_NEXT_IRQ SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 3) |
| + |
| +#define SMC_FC_CPU_SUSPEND SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 7) |
| +#define SMC_FC_CPU_RESUME SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 8) |
| + |
| +#define SMC_FC_AARCH_SWITCH SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 9) |
| +#define SMC_FC_GET_VERSION_STR SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 10) |
| + |
| +/** |
| + * SMC_FC_API_VERSION - Find and select supported API version. |
| + * |
| + * @r1: Version supported by client. |
| + * |
| + * Returns version supported by trusty. |
| + * |
| + * If multiple versions are supported, the client should start by calling |
| + * SMC_FC_API_VERSION with the largest version it supports. Trusty will then |
| + * return a version it supports. If the client does not support the version |
| + * returned by trusty and the version returned is less than the version |
| + * requested, repeat the call with the largest supported version less than the |
| + * last returned version. |
| + * |
| + * This call must be made before any calls that are affected by the api version. |
| + */ |
| +#define TRUSTY_API_VERSION_RESTART_FIQ (1) |
| +#define TRUSTY_API_VERSION_SMP (2) |
| +#define TRUSTY_API_VERSION_SMP_NOP (3) |
| +#define TRUSTY_API_VERSION_PHYS_MEM_OBJ (4) |
| +#define TRUSTY_API_VERSION_MEM_OBJ (5) |
| +#define TRUSTY_API_VERSION_CURRENT (5) |
| +#define SMC_FC_API_VERSION SMC_FASTCALL_NR(SMC_ENTITY_SECURE_MONITOR, 11) |
| + |
| +/* TRUSTED_OS entity calls */ |
| +#define SMC_SC_VIRTIO_GET_DESCR SMC_STDCALL_NR(SMC_ENTITY_TRUSTED_OS, 20) |
| +#define SMC_SC_VIRTIO_START SMC_STDCALL_NR(SMC_ENTITY_TRUSTED_OS, 21) |
| +#define SMC_SC_VIRTIO_STOP SMC_STDCALL_NR(SMC_ENTITY_TRUSTED_OS, 22) |
| + |
| +#define SMC_SC_VDEV_RESET SMC_STDCALL_NR(SMC_ENTITY_TRUSTED_OS, 23) |
| +#define SMC_SC_VDEV_KICK_VQ SMC_STDCALL_NR(SMC_ENTITY_TRUSTED_OS, 24) |
| +#define SMC_NC_VDEV_KICK_VQ SMC_STDCALL_NR(SMC_ENTITY_TRUSTED_OS, 25) |
| + |
| +#endif /* __LINUX_TRUSTY_SMCALL_H */ |
| diff --git a/include/linux/trusty/trusty.h b/include/linux/trusty/trusty.h |
| new file mode 100644 |
| index 000000000000..efbb36999a8b |
| --- /dev/null |
| +++ b/include/linux/trusty/trusty.h |
| @@ -0,0 +1,131 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (C) 2013 Google, Inc. |
| + */ |
| +#ifndef __LINUX_TRUSTY_TRUSTY_H |
| +#define __LINUX_TRUSTY_TRUSTY_H |
| + |
| +#include <linux/kernel.h> |
| +#include <linux/trusty/sm_err.h> |
| +#include <linux/types.h> |
| +#include <linux/device.h> |
| +#include <linux/pagemap.h> |
| + |
| + |
| +#if IS_ENABLED(CONFIG_TRUSTY) |
| +s32 trusty_std_call32(struct device *dev, u32 smcnr, u32 a0, u32 a1, u32 a2); |
| +s32 trusty_fast_call32(struct device *dev, u32 smcnr, u32 a0, u32 a1, u32 a2); |
| +#ifdef CONFIG_64BIT |
| +s64 trusty_fast_call64(struct device *dev, u64 smcnr, u64 a0, u64 a1, u64 a2); |
| +#endif |
| +#else |
| +static inline s32 trusty_std_call32(struct device *dev, u32 smcnr, |
| + u32 a0, u32 a1, u32 a2) |
| +{ |
| + return SM_ERR_UNDEFINED_SMC; |
| +} |
| +static inline s32 trusty_fast_call32(struct device *dev, u32 smcnr, |
| + u32 a0, u32 a1, u32 a2) |
| +{ |
| + return SM_ERR_UNDEFINED_SMC; |
| +} |
| +#ifdef CONFIG_64BIT |
| +static inline s64 trusty_fast_call64(struct device *dev, |
| + u64 smcnr, u64 a0, u64 a1, u64 a2) |
| +{ |
| + return SM_ERR_UNDEFINED_SMC; |
| +} |
| +#endif |
| +#endif |
| + |
| +struct notifier_block; |
| +enum { |
| + TRUSTY_CALL_PREPARE, |
| + TRUSTY_CALL_RETURNED, |
| +}; |
| +int trusty_call_notifier_register(struct device *dev, |
| + struct notifier_block *n); |
| +int trusty_call_notifier_unregister(struct device *dev, |
| + struct notifier_block *n); |
| +const char *trusty_version_str_get(struct device *dev); |
| +u32 trusty_get_api_version(struct device *dev); |
| +bool trusty_get_panic_status(struct device *dev); |
| + |
| +struct ns_mem_page_info { |
| + u64 paddr; |
| + u8 ffa_mem_attr; |
| + u8 ffa_mem_perm; |
| + u64 compat_attr; |
| +}; |
| + |
| +int trusty_encode_page_info(struct ns_mem_page_info *inf, |
| + struct page *page, pgprot_t pgprot); |
| + |
| +struct scatterlist; |
| +typedef u64 trusty_shared_mem_id_t; |
| +int trusty_share_memory(struct device *dev, trusty_shared_mem_id_t *id, |
| + struct scatterlist *sglist, unsigned int nents, |
| + pgprot_t pgprot); |
| +int trusty_share_memory_compat(struct device *dev, trusty_shared_mem_id_t *id, |
| + struct scatterlist *sglist, unsigned int nents, |
| + pgprot_t pgprot); |
| +int trusty_transfer_memory(struct device *dev, u64 *id, |
| + struct scatterlist *sglist, unsigned int nents, |
| + pgprot_t pgprot, u64 tag, bool lend); |
| +int trusty_reclaim_memory(struct device *dev, trusty_shared_mem_id_t id, |
| + struct scatterlist *sglist, unsigned int nents); |
| + |
| +struct dma_buf; |
| +#ifdef CONFIG_TRUSTY_DMA_BUF_FFA_TAG |
| +u64 trusty_dma_buf_get_ffa_tag(struct dma_buf *dma_buf); |
| +#else |
| +static inline u64 trusty_dma_buf_get_ffa_tag(struct dma_buf *dma_buf) |
| +{ |
| + return 0; |
| +} |
| +#endif |
| + |
| +/* Invalid handle value is defined by FF-A spec */ |
| +#ifdef CONFIG_TRUSTY_DMA_BUF_SHARED_MEM_ID |
| +/** |
| + * trusty_dma_buf_get_shared_mem_id() - Get memory ID corresponding to a dma_buf |
| + * @dma_buf: DMA buffer |
| + * @id: Pointer to output trusty_shared_mem_id_t |
| + * |
| + * Sets @id to trusty_shared_mem_id_t corresponding to the given @dma_buf. |
| + * @dma_buf "owns" the ID, i.e. is responsible for allocating/releasing it. |
| + * @dma_buf with an allocated @id must be in secure memory and should only be |
| + * sent to Trusty using TRUSTY_SEND_SECURE. |
| + * |
| + * Return: |
| + * * 0 - success |
| + * * -ENODATA - @dma_buf does not own a trusty_shared_mem_id_t |
| + * * ... - @dma_buf should not be lent or shared |
| + */ |
| +int trusty_dma_buf_get_shared_mem_id(struct dma_buf *dma_buf, |
| + trusty_shared_mem_id_t *id); |
| +#else |
| +static inline int trusty_dma_buf_get_shared_mem_id(struct dma_buf *dma_buf, |
| + trusty_shared_mem_id_t *id) |
| +{ |
| + return -ENODATA; |
| +} |
| +#endif |
| + |
| +struct trusty_nop { |
| + struct list_head node; |
| + u32 args[3]; |
| +}; |
| + |
| +static inline void trusty_nop_init(struct trusty_nop *nop, |
| + u32 arg0, u32 arg1, u32 arg2) { |
| + INIT_LIST_HEAD(&nop->node); |
| + nop->args[0] = arg0; |
| + nop->args[1] = arg1; |
| + nop->args[2] = arg2; |
| +} |
| + |
| +void trusty_enqueue_nop(struct device *dev, struct trusty_nop *nop); |
| +void trusty_dequeue_nop(struct device *dev, struct trusty_nop *nop); |
| + |
| +#endif |
| diff --git a/include/linux/trusty/trusty_ipc.h b/include/linux/trusty/trusty_ipc.h |
| new file mode 100644 |
| index 000000000000..9386392f3a64 |
| --- /dev/null |
| +++ b/include/linux/trusty/trusty_ipc.h |
| @@ -0,0 +1,89 @@ |
| +/* SPDX-License-Identifier: GPL-2.0-only */ |
| +/* |
| + * Copyright (C) 2015 Google, Inc. |
| + */ |
| +#ifndef __LINUX_TRUSTY_TRUSTY_IPC_H |
| +#define __LINUX_TRUSTY_TRUSTY_IPC_H |
| + |
| +#include <linux/list.h> |
| +#include <linux/scatterlist.h> |
| +#include <linux/trusty/trusty.h> |
| +#include <linux/types.h> |
| + |
| +struct tipc_chan; |
| + |
| +struct tipc_msg_buf { |
| + void *buf_va; |
| + struct scatterlist sg; |
| + trusty_shared_mem_id_t buf_id; |
| + size_t buf_sz; |
| + size_t wpos; |
| + size_t rpos; |
| + size_t shm_cnt; |
| + struct list_head node; |
| +}; |
| + |
| +enum tipc_chan_event { |
| + TIPC_CHANNEL_CONNECTED = 1, |
| + TIPC_CHANNEL_DISCONNECTED, |
| + TIPC_CHANNEL_SHUTDOWN, |
| +}; |
| + |
| +struct tipc_chan_ops { |
| + void (*handle_event)(void *cb_arg, int event); |
| + struct tipc_msg_buf *(*handle_msg)(void *cb_arg, |
| + struct tipc_msg_buf *mb); |
| + void (*handle_release)(void *cb_arg); |
| +}; |
| + |
| +struct tipc_chan *tipc_create_channel(struct device *dev, |
| + const struct tipc_chan_ops *ops, |
| + void *cb_arg); |
| + |
| +int tipc_chan_connect(struct tipc_chan *chan, const char *port); |
| + |
| +int tipc_chan_queue_msg(struct tipc_chan *chan, struct tipc_msg_buf *mb); |
| + |
| +int tipc_chan_shutdown(struct tipc_chan *chan); |
| + |
| +void tipc_chan_destroy(struct tipc_chan *chan); |
| + |
| +struct tipc_msg_buf *tipc_chan_get_rxbuf(struct tipc_chan *chan); |
| + |
| +void tipc_chan_put_rxbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb); |
| + |
| +struct tipc_msg_buf * |
| +tipc_chan_get_txbuf_timeout(struct tipc_chan *chan, long timeout); |
| + |
| +void tipc_chan_put_txbuf(struct tipc_chan *chan, struct tipc_msg_buf *mb); |
| + |
| +static inline size_t mb_avail_space(struct tipc_msg_buf *mb) |
| +{ |
| + return mb->buf_sz - mb->wpos; |
| +} |
| + |
| +static inline size_t mb_avail_data(struct tipc_msg_buf *mb) |
| +{ |
| + return mb->wpos - mb->rpos; |
| +} |
| + |
| +static inline void *mb_put_data(struct tipc_msg_buf *mb, size_t len) |
| +{ |
| + void *pos = (u8 *)mb->buf_va + mb->wpos; |
| + |
| + BUG_ON(mb->wpos + len > mb->buf_sz); |
| + mb->wpos += len; |
| + return pos; |
| +} |
| + |
| +static inline void *mb_get_data(struct tipc_msg_buf *mb, size_t len) |
| +{ |
| + void *pos = (u8 *)mb->buf_va + mb->rpos; |
| + |
| + BUG_ON(mb->rpos + len > mb->wpos); |
| + mb->rpos += len; |
| + return pos; |
| +} |
| + |
| +#endif /* __LINUX_TRUSTY_TRUSTY_IPC_H */ |
| + |
| diff --git a/include/uapi/linux/trusty/ipc.h b/include/uapi/linux/trusty/ipc.h |
| new file mode 100644 |
| index 000000000000..af91035484f1 |
| --- /dev/null |
| +++ b/include/uapi/linux/trusty/ipc.h |
| @@ -0,0 +1,65 @@ |
| +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ |
| + |
| +#ifndef _UAPI_LINUX_TRUSTY_IPC_H_ |
| +#define _UAPI_LINUX_TRUSTY_IPC_H_ |
| + |
| +#include <linux/ioctl.h> |
| +#include <linux/types.h> |
| +#include <linux/uio.h> |
| + |
| +/** |
| + * enum transfer_kind - How to send an fd to Trusty |
| + * @TRUSTY_SHARE: Memory will be accessible by Linux and Trusty. On ARM it |
| + * will be mapped as nonsecure. Suitable for shared memory. |
| + * The paired fd must be a "dma_buf". |
| + * @TRUSTY_LEND: Memory will be accessible only to Trusty. On ARM it will |
| + * be transitioned to "Secure" memory if Trusty is in |
| + * TrustZone. This transfer kind is suitable for donating |
| + * video buffers or other similar resources. The paired fd |
| + * may need to come from a platform-specific allocator for |
| + * memory that may be transitioned to "Secure". |
| + * @TRUSTY_SEND_SECURE: Send memory that is already "Secure". Memory will be |
| + * accessible only to Trusty. The paired fd may need to |
| + * come from a platform-specific allocator that returns |
| + * "Secure" buffers. |
| + * |
| + * Describes how the user would like the resource in question to be sent to |
| + * Trusty. Options may be valid only for certain kinds of fds. |
| + */ |
| +enum transfer_kind { |
| + TRUSTY_SHARE = 0, |
| + TRUSTY_LEND = 1, |
| + TRUSTY_SEND_SECURE = 2, |
| +}; |
| + |
| +/** |
| + * struct trusty_shm - Describes a transfer of memory to Trusty |
| + * @fd: The fd to transfer |
| + * @transfer: How to transfer it - see &enum transfer_kind |
| + */ |
| +struct trusty_shm { |
| + __s32 fd; |
| + __u32 transfer; |
| +}; |
| + |
| +/** |
| + * struct tipc_send_msg_req - Request struct for @TIPC_IOC_SEND_MSG |
| + * @iov: Pointer to an array of &struct iovec describing data to be sent |
| + * @shm: Pointer to an array of &struct trusty_shm describing any file |
| + * descriptors to be transferred. |
| + * @iov_cnt: Number of elements in the @iov array |
| + * @shm_cnt: Number of elements in the @shm array |
| + */ |
| +struct tipc_send_msg_req { |
| + __u64 iov; |
| + __u64 shm; |
| + __u64 iov_cnt; |
| + __u64 shm_cnt; |
| +}; |
| + |
| +#define TIPC_IOC_MAGIC 'r' |
| +#define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char *) |
| +#define TIPC_IOC_SEND_MSG _IOW(TIPC_IOC_MAGIC, 0x81, \ |
| + struct tipc_send_msg_req) |
| + |
| +#endif |
| diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h |
| index 80d76b75bccd..909905cd7618 100644 |
| --- a/include/uapi/linux/virtio_ids.h |
| +++ b/include/uapi/linux/virtio_ids.h |
| @@ -42,6 +42,7 @@ |
| #define VIRTIO_ID_RPROC_SERIAL 11 /* virtio remoteproc serial link */ |
| #define VIRTIO_ID_CAIF 12 /* Virtio caif */ |
| #define VIRTIO_ID_MEMORY_BALLOON 13 /* virtio memory balloon */ |
| +#define VIRTIO_ID_TRUSTY_IPC 13 /* virtio trusty ipc */ |
| #define VIRTIO_ID_GPU 16 /* virtio GPU */ |
| #define VIRTIO_ID_CLOCK 17 /* virtio clock/timer */ |
| #define VIRTIO_ID_INPUT 18 /* virtio input */ |
| -- |
| 2.34.1 |
| |