From 4e8ed5f7b776e2588dab1da63d60846a9a926382 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Mon, 9 Oct 2023 11:17:59 +0100 Subject: [PATCH] firmware: tegra: Add support iosys-map support for IVC Linux v6.2 update the Tegra IVC driver to use iosys-map and this broke support for the IVC EXT driver. Update the IVC EXT driver to support iosys-map if the kernel supports this version of the Tegra IVC driver. Bug 4221847 Change-Id: I1f1ddb1cc4312cb2359373b53287c110831b473c Signed-off-by: Jon Hunter Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2996208 Reviewed-by: Laxman Dewangan GVS: Gerrit_Virtual_Submit --- drivers/firmware/tegra/Makefile | 3 +- drivers/firmware/tegra/ivc_ext.c | 142 ++++++++++++++++++++++++++++++- include/soc/tegra/ivc_ext.h | 87 ++++++------------- 3 files changed, 168 insertions(+), 64 deletions(-) diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index 5bbfae69..4c88b620 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -8,9 +8,8 @@ LINUX_VERSION := $(shell expr $(VERSION) \* 256 + $(PATCHLEVEL)) LINUX_VERSION_6_2 := $(shell expr 6 \* 256 + 2) -# ivc_ext is currently broken for Linux v6.2 and so skip for Linux v6.2+ -ifeq ($(shell test $(LINUX_VERSION) -lt $(LINUX_VERSION_6_2); echo $$?),0) obj-m += ivc_ext.o +ifeq ($(shell test $(LINUX_VERSION) -lt $(LINUX_VERSION_6_2); echo $$?),0) tegra_bpmp-y += ../../clk/tegra/clk-bpmp.o tegra_bpmp-y += ../../reset/tegra/reset-bpmp.o tegra_bpmp-y += ../../soc/tegra/powergate-bpmp.o diff --git a/drivers/firmware/tegra/ivc_ext.c b/drivers/firmware/tegra/ivc_ext.c index 8e6dfa8f..7be90b85 100644 --- a/drivers/firmware/tegra/ivc_ext.c +++ b/drivers/firmware/tegra/ivc_ext.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ +#include + #include #include @@ -81,6 +83,14 @@ void tegra_ivc_channel_reset(struct tegra_ivc *ivc) } EXPORT_SYMBOL(tegra_ivc_channel_reset); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +#define tegra_ivc_header_read_field(hdr, field) \ + iosys_map_rd_field(hdr, 0, struct tegra_ivc_header, field) + +#define tegra_ivc_header_write_field(hdr, field, value) \ + iosys_map_wr_field(hdr, 0, struct tegra_ivc_header, field, value) +#endif + static inline void tegra_ivc_invalidate(struct tegra_ivc *ivc, dma_addr_t phys) { if (!ivc->peer) @@ -90,16 +100,25 @@ static inline void tegra_ivc_invalidate(struct tegra_ivc *ivc, dma_addr_t phys) DMA_FROM_DEVICE); } +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +bool tegra_ivc_empty(struct tegra_ivc *ivc, struct iosys_map *map) +#else bool tegra_ivc_empty(struct tegra_ivc *ivc, struct tegra_ivc_header *header) +#endif { /* * This function performs multiple checks on the same values with * security implications, so create snapshots with READ_ONCE() to * ensure that these checks use the same values. */ +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + u32 tx = tegra_ivc_header_read_field(map, tx.count); + u32 rx = tegra_ivc_header_read_field(map, rx.count); +#else u32 tx = READ_ONCE(header->tx.count); u32 rx = READ_ONCE(header->rx.count); +#endif /* * Perform an over-full check to prevent denial of service attacks @@ -118,11 +137,20 @@ bool tegra_ivc_empty(struct tegra_ivc *ivc, } EXPORT_SYMBOL(tegra_ivc_empty); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +static inline bool tegra_ivc_full(struct tegra_ivc *ivc, struct iosys_map *map) +#else static inline bool tegra_ivc_full(struct tegra_ivc *ivc, struct tegra_ivc_header *header) +#endif { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + u32 tx = tegra_ivc_header_read_field(map, tx.count); + u32 rx = tegra_ivc_header_read_field(map, rx.count); +#else u32 tx = READ_ONCE(header->tx.count); u32 rx = READ_ONCE(header->rx.count); +#endif /* * Invalid cases where the counters indicate that the queue is over @@ -134,6 +162,8 @@ static inline bool tegra_ivc_full(struct tegra_ivc *ivc, static inline int tegra_ivc_check_read(struct tegra_ivc *ivc) { unsigned int offset = offsetof(struct tegra_ivc_header, tx.count); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + unsigned int state; /* * tx.channel->state is set locally, so it is not synchronized with @@ -143,7 +173,11 @@ static inline int tegra_ivc_check_read(struct tegra_ivc *ivc) * asynchronous transition of rx.channel->state to * TEGRA_IVC_STATE_ACK is not allowed. */ + state = tegra_ivc_header_read_field(&ivc->tx.map, tx.state); + if (state != TEGRA_IVC_STATE_ESTABLISHED) +#else if (ivc->tx.channel->tx.state != TEGRA_IVC_STATE_ESTABLISHED) +#endif return -ECONNRESET; /* @@ -153,12 +187,20 @@ static inline int tegra_ivc_check_read(struct tegra_ivc *ivc) * Synchronization is only necessary when these pointers indicate * empty or full. */ +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + if (!tegra_ivc_empty(ivc, &ivc->rx.map)) +#else if (!tegra_ivc_empty(ivc, ivc->rx.channel)) +#endif return 0; tegra_ivc_invalidate(ivc, ivc->rx.phys + offset); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + if (!tegra_ivc_empty(ivc, &ivc->rx.map)) +#else if (tegra_ivc_empty(ivc, ivc->rx.channel)) +#endif return -ENOSPC; return 0; @@ -167,16 +209,30 @@ static inline int tegra_ivc_check_read(struct tegra_ivc *ivc) static inline int tegra_ivc_check_write(struct tegra_ivc *ivc) { unsigned int offset = offsetof(struct tegra_ivc_header, rx.count); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + unsigned int state; + state = tegra_ivc_header_read_field(&ivc->tx.map, tx.state); + if (state != TEGRA_IVC_STATE_ESTABLISHED) +#else if (ivc->tx.channel->tx.state != TEGRA_IVC_STATE_ESTABLISHED) +#endif return -ECONNRESET; +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + if (!tegra_ivc_full(ivc, &ivc->tx.map)) +#else if (!tegra_ivc_full(ivc, ivc->tx.channel)) +#endif return 0; tegra_ivc_invalidate(ivc, ivc->tx.phys + offset); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + if (!tegra_ivc_full(ivc, &ivc->tx.map)) +#else if (tegra_ivc_full(ivc, ivc->tx.channel)) +#endif return -ENOSPC; return 0; @@ -196,21 +252,41 @@ EXPORT_SYMBOL(tegra_ivc_can_write); int tegra_ivc_read(struct tegra_ivc *ivc, void __user *usr_buf, void *buf, size_t max_read) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + struct iosys_map map; + int err; +#else void *frame; +#endif BUG_ON(buf && usr_buf); /* get next frame to be read from IVC channel */ +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + err = tegra_ivc_read_get_next_frame(ivc, &map); + if (err) + return err; +#else frame = tegra_ivc_read_get_next_frame(ivc); if (IS_ERR(frame)) { return PTR_ERR(frame); } +#endif /* update the buffer with read data*/ if (buf) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + iosys_map_memcpy_from(buf, &map, 0, max_read); +#else memcpy(buf, frame, max_read); +#endif } else if (usr_buf) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + // FIXME handle io address space + if (WARN_ON(map.is_iomem) || copy_to_user(usr_buf, map.vaddr, max_read)) +#else if (copy_to_user(usr_buf, frame, max_read)) +#endif return -EFAULT; } else BUG(); @@ -225,21 +301,41 @@ EXPORT_SYMBOL(tegra_ivc_read); int tegra_ivc_read_peek(struct tegra_ivc *ivc, void __user *usr_buf, void *buf, size_t offset, size_t size) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + struct iosys_map map; + int err; +#else void *frame; +#endif BUG_ON(buf && usr_buf); /* get next frame to be read from IVC channel */ +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + err = tegra_ivc_read_get_next_frame(ivc, &map); + if (err) + return err; +#else frame = tegra_ivc_read_get_next_frame(ivc); if (IS_ERR(frame)) { return PTR_ERR(frame); } +#endif /* update the buffer with read data*/ if (buf) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + iosys_map_memcpy_from(buf, &map, offset, size); +#else memcpy(buf, frame + offset, size); +#endif } else if (usr_buf) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + // FIXME handle io address space + if (WARN_ON(map.is_iomem) || copy_to_user(usr_buf, map.vaddr + offset, size)) +#else if (copy_to_user(usr_buf, frame + offset, size)) +#endif return -EFAULT; } else BUG(); @@ -250,21 +346,41 @@ EXPORT_SYMBOL(tegra_ivc_read_peek); int tegra_ivc_write(struct tegra_ivc *ivc, const void __user *usr_buf, const void *buf, size_t size) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + struct iosys_map map; + int err; +#else void *frame; +#endif BUG_ON(buf && usr_buf); /* get next frame to be written from IVC channel */ +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + err = tegra_ivc_write_get_next_frame(ivc, &map); + if (err) + return err; +#else frame = tegra_ivc_write_get_next_frame(ivc); if (IS_ERR(frame)) { return PTR_ERR(frame); } +#endif /* update the write frame with data buffer*/ if (buf) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + iosys_map_memcpy_to(&map, 0, buf, size); +#else memcpy(frame, buf, size); +#endif } else if (usr_buf) { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + // FIXME handle io address space + if (WARN_ON(map.is_iomem) || copy_from_user(map.vaddr, usr_buf, size)) +#else if (copy_from_user(frame, usr_buf, size)) +#endif return -EFAULT; } else BUG(); @@ -282,18 +398,35 @@ int tegra_ivc_channel_sync(struct tegra_ivc *ivc) if ((ivc == NULL) || (ivc->num_frames == 0)) { return -EINVAL; } else { +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) + u32 count; + + count = tegra_ivc_header_read_field(&ivc->tx.map, tx.count); + ivc->tx.position = count % ivc->num_frames; + count = tegra_ivc_header_read_field(&ivc->rx.map, rx.count); + ivc->rx.position = count % ivc->num_frames; +#else ivc->tx.position = ivc->tx.channel->tx.count % ivc->num_frames; ivc->rx.position = ivc->rx.channel->rx.count % ivc->num_frames; +#endif } + return 0; } EXPORT_SYMBOL(tegra_ivc_channel_sync); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +static inline u32 tegra_ivc_available(struct tegra_ivc *ivc, struct iosys_map *map) +{ + u32 tx = tegra_ivc_header_read_field(map, tx.count); + u32 rx = tegra_ivc_header_read_field(map, rx.count); +#else static inline u32 tegra_ivc_available(struct tegra_ivc *ivc, struct tegra_ivc_header *header) { u32 tx = READ_ONCE(header->tx.count); u32 rx = READ_ONCE(header->rx.count); +#endif /* * This function isn't expected to be used in scenarios where an @@ -304,10 +437,17 @@ static inline u32 tegra_ivc_available(struct tegra_ivc *ivc, return tx - rx; } +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +uint32_t tegra_ivc_frames_available(struct tegra_ivc *ivc, struct iosys_map *map) +{ + return (ivc->num_frames - tegra_ivc_available(ivc, map)); +} +#else uint32_t tegra_ivc_frames_available(struct tegra_ivc *ivc, struct tegra_ivc_header *header) { return (ivc->num_frames - tegra_ivc_available(ivc, header)); } +#endif EXPORT_SYMBOL(tegra_ivc_frames_available); /* Inserting this driver as module to export diff --git a/include/soc/tegra/ivc_ext.h b/include/soc/tegra/ivc_ext.h index 91aac1d0..3b4e6854 100644 --- a/include/soc/tegra/ivc_ext.h +++ b/include/soc/tegra/ivc_ext.h @@ -6,11 +6,11 @@ #ifndef __TEGRA_IVC_EXT_H #define __TEGRA_IVC_EXT_H +#include + #include -#include #include -#if (KERNEL_VERSION(6, 2, 0) > LINUX_VERSION_CODE) /** * tegra_ivc_channel_notified - notifies the peer device * @ivc pointer of the IVC channel @@ -29,6 +29,17 @@ int tegra_ivc_channel_notified(struct tegra_ivc *ivc); */ void tegra_ivc_channel_reset(struct tegra_ivc *ivc); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +/** + * tegra_ivc_empty - Checks whether channel is empty or not + * @map pointer to iosys-map buffer for the IVC channel + * + * Checks whether channel is empty or not to read or write + * + * Returns true if channel is empty to read or write. + */ +bool tegra_ivc_empty(struct tegra_ivc *ivc, struct iosys_map *map); +#else /** * tegra_ivc_empty - Checks whether channel is empty or not * @ivc pointer of the IVC channel @@ -38,6 +49,7 @@ void tegra_ivc_channel_reset(struct tegra_ivc *ivc); * Returns true if channel is empty to read or write. */ bool tegra_ivc_empty(struct tegra_ivc *ivc, struct tegra_ivc_header *header); +#endif /** * tegra_ivc_channel_sync - Syncs the IVC channel accross reboots. @@ -49,6 +61,17 @@ bool tegra_ivc_empty(struct tegra_ivc *ivc, struct tegra_ivc_header *header); */ int tegra_ivc_channel_sync(struct tegra_ivc *ivc); +#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) +/** + * tegra_ivc_frames_available - Checks number of available frames. + * @map pointer to iosys-map buffer for the IVC channel + * + * Checks number of available frames. + * + * Returns integer value indicating number of available frames.. + */ +uint32_t tegra_ivc_frames_available(struct tegra_ivc *ivc, struct iosys_map *map); +#else /** * tegra_ivc_frames_available - Checks number of available frames. * @ivc pointer of the IVC channel @@ -58,6 +81,7 @@ int tegra_ivc_channel_sync(struct tegra_ivc *ivc); * Returns integer value indicating number of available frames.. */ uint32_t tegra_ivc_frames_available(struct tegra_ivc *ivc, struct tegra_ivc_header *header); +#endif /** * tegra_ivc_can_read - Checks whether we can read from ivc channel @@ -118,64 +142,5 @@ int tegra_ivc_read_peek(struct tegra_ivc *ivc, void __user *usr_buf, void *buf, * Returns no. of bytes written to ivc channel else return error. */ int tegra_ivc_write(struct tegra_ivc *ivc, const void __user *usr_buf, const void *buf, size_t size); -#else -static inline int tegra_ivc_channel_notified(struct tegra_ivc *ivc) -{ - return -ENOTSUPP; -} - -static inline void tegra_ivc_channel_reset(struct tegra_ivc *ivc) -{ - return; -} - -static inline bool tegra_ivc_empty(struct tegra_ivc *ivc, - struct tegra_ivc_header *header) -{ - return true; -} - -static inline int tegra_ivc_channel_sync(struct tegra_ivc *ivc) -{ - return -ENOTSUPP; -} - -static inline uint32_t tegra_ivc_frames_available(struct tegra_ivc *ivc, - struct tegra_ivc_header *header) -{ - return 0; -} - -static inline int tegra_ivc_can_read(struct tegra_ivc *ivc) -{ - return -ENOTSUPP; -}; - -static inline int tegra_ivc_can_write(struct tegra_ivc *ivc) -{ - return -ENOTSUPP; -}; - -static inline int tegra_ivc_read(struct tegra_ivc *ivc, void __user *usr_buf, - void *buf, size_t max_read) -{ - return -ENOTSUPP; -} - -static inline int tegra_ivc_read_peek(struct tegra_ivc *ivc, - void __user *usr_buf, void *buf, - size_t offset, size_t size) -{ - return -ENOTSUPP; -}; - -static inline int tegra_ivc_write(struct tegra_ivc *ivc, - const void __user *usr_buf, - const void *buf, size_t size) -{ - return -ENOTSUPP; -}; - -#endif /* (KERNEL_VERSION(6, 2, 0) > LINUX_VERSION_CODE) */ #endif /* __TEGRA_IVC_EXT_H */