From 0388634a6ce9f0167747a3abac29001e2abc3879 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 18 Jul 2016 12:15:03 -0600 Subject: ARM: tegra: fix Tegra186 DT GPIO binding header Tegra186 uses different GPIO port IDs compared to previous chips. Make sure the SoC DT file includes the correct GPIO binding header. Signed-off-by: Stephen Warren Signed-off-by: Tom Warren --- arch/arm/dts/tegra186.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'arch') diff --git a/arch/arm/dts/tegra186.dtsi b/arch/arm/dts/tegra186.dtsi index fce34fa6500..99d49254b36 100644 --- a/arch/arm/dts/tegra186.dtsi +++ b/arch/arm/dts/tegra186.dtsi @@ -1,5 +1,5 @@ #include "skeleton.dtsi" -#include +#include #include #include -- cgit v1.2.3 From 1f60f0731db2e07eecc41afa92bee4b10e7d9f1f Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 18 Jul 2016 12:15:04 -0600 Subject: ARM: tegra: split p2771-0000 build There are multiple versions of p2771-0000 board. There are SW visible incompatible differences between the versions, and they are relevant to U-Boot. Create separate "A02" and "B00" defconfigs (named after the first and/or only board rev the defconfig supports) so that users can select which build they want. With the minimal set of HW currently enabled in U-Boot, the differences are irrelevant, hence the DT files aren't different. However, that will change in a future patch. Signed-off-by: Stephen Warren Signed-off-by: Tom Warren --- arch/arm/dts/Makefile | 3 ++- arch/arm/dts/tegra186-p2771-0000-a02.dts | 8 ++++++++ arch/arm/dts/tegra186-p2771-0000-b00.dts | 8 ++++++++ arch/arm/dts/tegra186-p2771-0000.dts | 25 ------------------------- arch/arm/dts/tegra186-p2771-0000.dtsi | 23 +++++++++++++++++++++++ configs/p2771-0000-a02_defconfig | 31 +++++++++++++++++++++++++++++++ configs/p2771-0000-b00_defconfig | 31 +++++++++++++++++++++++++++++++ configs/p2771-0000_defconfig | 31 ------------------------------- 8 files changed, 103 insertions(+), 57 deletions(-) create mode 100644 arch/arm/dts/tegra186-p2771-0000-a02.dts create mode 100644 arch/arm/dts/tegra186-p2771-0000-b00.dts delete mode 100644 arch/arm/dts/tegra186-p2771-0000.dts create mode 100644 arch/arm/dts/tegra186-p2771-0000.dtsi create mode 100644 configs/p2771-0000-a02_defconfig create mode 100644 configs/p2771-0000-b00_defconfig delete mode 100644 configs/p2771-0000_defconfig (limited to 'arch') diff --git a/arch/arm/dts/Makefile b/arch/arm/dts/Makefile index acecb7c3594..ca8712a7a74 100644 --- a/arch/arm/dts/Makefile +++ b/arch/arm/dts/Makefile @@ -53,7 +53,8 @@ dtb-$(CONFIG_TEGRA) += tegra20-harmony.dtb \ tegra124-jetson-tk1.dtb \ tegra124-nyan-big.dtb \ tegra124-venice2.dtb \ - tegra186-p2771-0000.dtb \ + tegra186-p2771-0000-a02.dtb \ + tegra186-p2771-0000-b00.dtb \ tegra210-e2220-1170.dtb \ tegra210-p2371-0000.dtb \ tegra210-p2371-2180.dtb \ diff --git a/arch/arm/dts/tegra186-p2771-0000-a02.dts b/arch/arm/dts/tegra186-p2771-0000-a02.dts new file mode 100644 index 00000000000..70f4326c091 --- /dev/null +++ b/arch/arm/dts/tegra186-p2771-0000-a02.dts @@ -0,0 +1,8 @@ +/dts-v1/; + +#include "tegra186-p2771-0000.dtsi" + +/ { + model = "NVIDIA P2771-0000 A02"; + compatible = "nvidia,p2771-0000-a02", "nvidia,p2771-0000", "nvidia,tegra186"; +}; diff --git a/arch/arm/dts/tegra186-p2771-0000-b00.dts b/arch/arm/dts/tegra186-p2771-0000-b00.dts new file mode 100644 index 00000000000..2384a65e870 --- /dev/null +++ b/arch/arm/dts/tegra186-p2771-0000-b00.dts @@ -0,0 +1,8 @@ +/dts-v1/; + +#include "tegra186-p2771-0000.dtsi" + +/ { + model = "NVIDIA P2771-0000 B00"; + compatible = "nvidia,p2771-0000-b00", "nvidia,p2771-0000", "nvidia,tegra186"; +}; diff --git a/arch/arm/dts/tegra186-p2771-0000.dts b/arch/arm/dts/tegra186-p2771-0000.dts deleted file mode 100644 index 5f29ee45019..00000000000 --- a/arch/arm/dts/tegra186-p2771-0000.dts +++ /dev/null @@ -1,25 +0,0 @@ -/dts-v1/; - -#include "tegra186.dtsi" - -/ { - model = "NVIDIA P2771-0000"; - compatible = "nvidia,p2771-0000", "nvidia,tegra186"; - - chosen { - stdout-path = &uarta; - }; - - aliases { - sdhci0 = "/sdhci@3460000"; - }; - - memory { - reg = <0x0 0x80000000 0x0 0x60000000>; - }; - - sdhci@3460000 { - status = "okay"; - bus-width = <8>; - }; -}; diff --git a/arch/arm/dts/tegra186-p2771-0000.dtsi b/arch/arm/dts/tegra186-p2771-0000.dtsi new file mode 100644 index 00000000000..87f0427e80c --- /dev/null +++ b/arch/arm/dts/tegra186-p2771-0000.dtsi @@ -0,0 +1,23 @@ +#include "tegra186.dtsi" + +/ { + model = "NVIDIA P2771-0000"; + compatible = "nvidia,p2771-0000", "nvidia,tegra186"; + + chosen { + stdout-path = &uarta; + }; + + aliases { + sdhci0 = "/sdhci@3460000"; + }; + + memory { + reg = <0x0 0x80000000 0x0 0x60000000>; + }; + + sdhci@3460000 { + status = "okay"; + bus-width = <8>; + }; +}; diff --git a/configs/p2771-0000-a02_defconfig b/configs/p2771-0000-a02_defconfig new file mode 100644 index 00000000000..1fe25f58f19 --- /dev/null +++ b/configs/p2771-0000-a02_defconfig @@ -0,0 +1,31 @@ +CONFIG_ARM=y +CONFIG_TEGRA=y +CONFIG_TEGRA186=y +CONFIG_TARGET_P2771_0000=y +CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000-a02" +CONFIG_OF_SYSTEM_SETUP=y +CONFIG_HUSH_PARSER=y +CONFIG_SYS_PROMPT="Tegra186 (P2771-0000 A02) # " +# CONFIG_CMD_IMI is not set +# CONFIG_CMD_IMLS is not set +# CONFIG_CMD_FLASH is not set +CONFIG_CMD_MMC=y +CONFIG_CMD_SF=y +CONFIG_CMD_SPI=y +CONFIG_CMD_I2C=y +CONFIG_CMD_USB=y +# CONFIG_CMD_FPGA is not set +CONFIG_CMD_GPIO=y +# CONFIG_CMD_SETEXPR is not set +CONFIG_CMD_DHCP=y +# CONFIG_CMD_NFS is not set +CONFIG_CMD_MII=y +CONFIG_CMD_PING=y +CONFIG_CMD_EXT2=y +CONFIG_CMD_EXT4=y +CONFIG_CMD_EXT4_WRITE=y +CONFIG_CMD_FAT=y +CONFIG_CMD_FS_GENERIC=y +CONFIG_SYS_NS16550=y +CONFIG_USB=y +CONFIG_DM_USB=y diff --git a/configs/p2771-0000-b00_defconfig b/configs/p2771-0000-b00_defconfig new file mode 100644 index 00000000000..552fb6cec78 --- /dev/null +++ b/configs/p2771-0000-b00_defconfig @@ -0,0 +1,31 @@ +CONFIG_ARM=y +CONFIG_TEGRA=y +CONFIG_TEGRA186=y +CONFIG_TARGET_P2771_0000=y +CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000-b00" +CONFIG_OF_SYSTEM_SETUP=y +CONFIG_HUSH_PARSER=y +CONFIG_SYS_PROMPT="Tegra186 (P2771-0000 B00) # " +# CONFIG_CMD_IMI is not set +# CONFIG_CMD_IMLS is not set +# CONFIG_CMD_FLASH is not set +CONFIG_CMD_MMC=y +CONFIG_CMD_SF=y +CONFIG_CMD_SPI=y +CONFIG_CMD_I2C=y +CONFIG_CMD_USB=y +# CONFIG_CMD_FPGA is not set +CONFIG_CMD_GPIO=y +# CONFIG_CMD_SETEXPR is not set +CONFIG_CMD_DHCP=y +# CONFIG_CMD_NFS is not set +CONFIG_CMD_MII=y +CONFIG_CMD_PING=y +CONFIG_CMD_EXT2=y +CONFIG_CMD_EXT4=y +CONFIG_CMD_EXT4_WRITE=y +CONFIG_CMD_FAT=y +CONFIG_CMD_FS_GENERIC=y +CONFIG_SYS_NS16550=y +CONFIG_USB=y +CONFIG_DM_USB=y diff --git a/configs/p2771-0000_defconfig b/configs/p2771-0000_defconfig deleted file mode 100644 index 9f2c418f9fe..00000000000 --- a/configs/p2771-0000_defconfig +++ /dev/null @@ -1,31 +0,0 @@ -CONFIG_ARM=y -CONFIG_TEGRA=y -CONFIG_TEGRA186=y -CONFIG_TARGET_P2771_0000=y -CONFIG_DEFAULT_DEVICE_TREE="tegra186-p2771-0000" -CONFIG_OF_SYSTEM_SETUP=y -CONFIG_HUSH_PARSER=y -CONFIG_SYS_PROMPT="Tegra186 (P2771-0000) # " -# CONFIG_CMD_IMI is not set -# CONFIG_CMD_IMLS is not set -# CONFIG_CMD_FLASH is not set -CONFIG_CMD_MMC=y -CONFIG_CMD_SF=y -CONFIG_CMD_SPI=y -CONFIG_CMD_I2C=y -CONFIG_CMD_USB=y -# CONFIG_CMD_FPGA is not set -CONFIG_CMD_GPIO=y -# CONFIG_CMD_SETEXPR is not set -CONFIG_CMD_DHCP=y -# CONFIG_CMD_NFS is not set -CONFIG_CMD_MII=y -CONFIG_CMD_PING=y -CONFIG_CMD_EXT2=y -CONFIG_CMD_EXT4=y -CONFIG_CMD_EXT4_WRITE=y -CONFIG_CMD_FAT=y -CONFIG_CMD_FS_GENERIC=y -CONFIG_SYS_NS16550=y -CONFIG_USB=y -CONFIG_DM_USB=y -- cgit v1.2.3 From d0f45000babf0c89b75b4c779613afa0a900a608 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 18 Jul 2016 12:15:05 -0600 Subject: ARM: tegra: unify Tegra186 Makefile a bit Many files in arch/arm/mach-tegra are compiled conditionally based on Kconfig variables, or applicable to all platforms. We can let the main Tegra Makefile handle compiling (or not) those files to avoid each SoC- specific Makefile needing to duplicate entries for those files. This leaves the SoC-specific Makefiles to compile truly SoC-specific code. In the future, we'll hopefully add Kconfig variables for all the other files, and refactor those files, and so reduce the need for SoC-specific Makefiles and/or ifdefs in the Makefiles. Signed-off-by: Stephen Warren Signed-off-by: Tom Warren --- arch/arm/mach-tegra/Makefile | 12 ++++++------ arch/arm/mach-tegra/tegra186/Makefile | 3 --- 2 files changed, 6 insertions(+), 9 deletions(-) (limited to 'arch') diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 12ee1cd7495..3b8d0127c7a 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -15,23 +15,23 @@ else obj-$(CONFIG_CMD_ENTERRCM) += cmd_enterrcm.o endif -obj-$(CONFIG_ARM64) += arm64-mmu.o obj-y += ap.o obj-y += board.o board2.o obj-y += cache.o obj-y += clock.o -obj-y += lowlevel_init.o obj-y += pinmux-common.o obj-y += powergate.o obj-y += xusb-padctl-dummy.o -obj-$(CONFIG_DISPLAY_CPUINFO) += sys_info.o -obj-$(CONFIG_TEGRA_GPU) += gpu.o -obj-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o +endif +obj-$(CONFIG_ARM64) += arm64-mmu.o +obj-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o +obj-$(CONFIG_TEGRA_GPU) += gpu.o +obj-y += lowlevel_init.o ifndef CONFIG_SPL_BUILD obj-$(CONFIG_ARMV7_PSCI) += psci.o endif -endif +obj-$(CONFIG_DISPLAY_CPUINFO) += sys_info.o obj-$(CONFIG_TEGRA20) += tegra20/ obj-$(CONFIG_TEGRA30) += tegra30/ diff --git a/arch/arm/mach-tegra/tegra186/Makefile b/arch/arm/mach-tegra/tegra186/Makefile index ce4610d8f80..188b097d2c7 100644 --- a/arch/arm/mach-tegra/tegra186/Makefile +++ b/arch/arm/mach-tegra/tegra186/Makefile @@ -2,7 +2,4 @@ # # SPDX-License-Identifier: GPL-2.0 -obj-y += ../arm64-mmu.o obj-y += ../board186.o -obj-y += ../lowlevel_init.o -obj-$(CONFIG_DISPLAY_CPUINFO) += ../sys_info.o -- cgit v1.2.3 From 49626ea801e8bb33c9ee4cbcb69e2fea6b13c330 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 18 Jul 2016 12:17:11 -0600 Subject: ARM: tegra: add IVC protocol implementation IVC (Inter-VM Communication) protocol is a Tegra-specific IPC (Inter Processor Communication) framework. Within the context of U-Boot, it is typically used for communication between the main CPU and various auxiliary processors. In particular, it will be used to communicate with the BPMP (Boot and Power Management Processor) on Tegra186 in order to manipulate clocks and reset signals. Signed-off-by: Stephen Warren Signed-off-by: Tom Warren --- arch/arm/include/asm/arch-tegra/ivc.h | 179 +++++++++++ arch/arm/mach-tegra/Kconfig | 9 + arch/arm/mach-tegra/Makefile | 1 + arch/arm/mach-tegra/ivc.c | 553 ++++++++++++++++++++++++++++++++++ 4 files changed, 742 insertions(+) create mode 100644 arch/arm/include/asm/arch-tegra/ivc.h create mode 100644 arch/arm/mach-tegra/ivc.c (limited to 'arch') diff --git a/arch/arm/include/asm/arch-tegra/ivc.h b/arch/arm/include/asm/arch-tegra/ivc.h new file mode 100644 index 00000000000..7f2287ae280 --- /dev/null +++ b/arch/arm/include/asm/arch-tegra/ivc.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#ifndef _ASM_ARCH_TEGRA_IVC_H +#define _ASM_ARCH_TEGRA_IVC_H + +#include + +/* + * Tegra IVC is a communication protocol that transfers fixed-size frames + * bi-directionally and in-order between the local CPU and some remote entity. + * Communication is via a statically sized and allocated buffer in shared + * memory and a notification mechanism. + * + * This API handles all aspects of the shared memory buffer's metadata, and + * leaves all aspects of the frame content to the calling code; frames + * typically contain some higher-level protocol. The notification mechanism is + * also handled externally to this API, since it can vary from instance to + * instance. + * + * The client model is to first find some free (for TX) or filled (for RX) + * frame, process that frame's memory buffer (fill or read it), and then + * inform the protocol that the frame has been filled/read, i.e. advance the + * write/read pointer. If the channel is full, there may be no available frames + * to fill/read. In this case, client code may either poll for an available + * frame, or wait for the remote entity to send a notification to the local + * CPU. + */ + +/** + * struct tegra_ivc - In-memory shared memory layout. + * + * This is described in detail in ivc.c. + */ +struct tegra_ivc_channel_header; + +/** + * struct tegra_ivc - Software state of an IVC channel. + * + * This state is internal to the IVC code and should not be accessed directly + * by clients. It is public solely so clients can allocate storage for the + * structure. + */ +struct tegra_ivc { + /** + * rx_channel - Pointer to the shared memory region used to receive + * messages from the remote entity. + */ + struct tegra_ivc_channel_header *rx_channel; + /** + * tx_channel - Pointer to the shared memory region used to send + * messages to the remote entity. + */ + struct tegra_ivc_channel_header *tx_channel; + /** + * r_pos - The position in list of frames in rx_channel that we are + * reading from. + */ + uint32_t r_pos; + /** + * w_pos - The position in list of frames in tx_channel that we are + * writing to. + */ + uint32_t w_pos; + /** + * nframes - The number of frames allocated (in each direction) in + * shared memory. + */ + uint32_t nframes; + /** + * frame_size - The size of each frame in shared memory. + */ + uint32_t frame_size; + /** + * notify - Function to call to notify the remote processor of a + * change in channel state. + */ + void (*notify)(struct tegra_ivc *); +}; + +/** + * tegra_ivc_read_get_next_frame - Locate the next frame to receive. + * + * Locate the next frame to be received/processed, return the address of the + * frame, and do not remove it from the queue. Repeated calls to this function + * will return the same address until tegra_ivc_read_advance() is called. + * + * @ivc The IVC channel. + * @frame Pointer to be filled with the address of the frame to receive. + * + * @return 0 if a frame is available, else a negative error code. + */ +int tegra_ivc_read_get_next_frame(struct tegra_ivc *ivc, void **frame); + +/** + * tegra_ivc_read_advance - Advance the read queue. + * + * Inform the protocol and remote entity that the frame returned by + * tegra_ivc_read_get_next_frame() has been processed. The remote end may then + * re-use it to transmit further data. Subsequent to this function returning, + * tegra_ivc_read_get_next_frame() will return a different frame. + * + * @ivc The IVC channel. + * + * @return 0 if OK, else a negative error code. + */ +int tegra_ivc_read_advance(struct tegra_ivc *ivc); + +/** + * tegra_ivc_write_get_next_frame - Locate the next frame to fill for transmit. + * + * Locate the next frame to be filled for transmit, return the address of the + * frame, and do not add it to the queue. Repeated calls to this function + * will return the same address until tegra_ivc_read_advance() is called. + * + * @ivc The IVC channel. + * @frame Pointer to be filled with the address of the frame to fill. + * + * @return 0 if a frame is available, else a negative error code. + */ +int tegra_ivc_write_get_next_frame(struct tegra_ivc *ivc, void **frame); + +/** + * tegra_ivc_write_advance - Advance the write queue. + * + * Inform the protocol and remote entity that the frame returned by + * tegra_ivc_write_get_next_frame() has been filled and should be transmitted. + * The remote end may then read data from it. Subsequent to this function + * returning, tegra_ivc_write_get_next_frame() will return a different frame. + * + * @ivc The IVC channel. + * + * @return 0 if OK, else a negative error code. + */ +int tegra_ivc_write_advance(struct tegra_ivc *ivc); + +/** + * tegra_ivc_channel_notified - handle internal messages + * + * This function must be called following every notification. + * + * @ivc The IVC channel. + * + * @return 0 if the channel is ready for communication, or -EAGAIN if a + * channel reset is in progress. + */ +int tegra_ivc_channel_notified(struct tegra_ivc *ivc); + +/** + * tegra_ivc_channel_reset - initiates a reset of the shared memory state + * + * This function must be called after a channel is initialized but before it + * is used for communication. The channel will be ready for use when a + * subsequent call to notify the remote of the channel reset indicates the + * reset operation is complete. + * + * @ivc The IVC channel. + */ +void tegra_ivc_channel_reset(struct tegra_ivc *ivc); + +/** + * tegra_ivc_init - Initialize a channel's software state. + * + * @ivc The IVC channel. + * @rx_base Address of the the RX shared memory buffer. + * @tx_base Address of the the TX shared memory buffer. + * @nframes Number of frames in each shared memory buffer. + * @frame_size Size of each frame. + * + * @return 0 if OK, else a negative error code. + */ +int tegra_ivc_init(struct tegra_ivc *ivc, ulong rx_base, ulong tx_base, + uint32_t nframes, uint32_t frame_size, + void (*notify)(struct tegra_ivc *)); + +#endif diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index f4affa5512b..85ae3b7f420 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -1,5 +1,13 @@ if TEGRA +config TEGRA_IVC + bool "Tegra IVC protocol" + help + IVC (Inter-VM Communication) protocol is a Tegra-specific IPC + (Inter Processor Communication) framework. Within the context of + U-Boot, it is typically used for communication between the main CPU + and various auxiliary processors. + config TEGRA_COMMON bool "Tegra common options" select DM @@ -60,6 +68,7 @@ config TEGRA186 select TEGRA186_GPIO select TEGRA_ARMV8_COMMON select TEGRA_HSP + select TEGRA_IVC endchoice diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 3b8d0127c7a..d0bf5a6e75e 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -27,6 +27,7 @@ endif obj-$(CONFIG_ARM64) += arm64-mmu.o obj-$(CONFIG_TEGRA_CLOCK_SCALING) += emc.o obj-$(CONFIG_TEGRA_GPU) += gpu.o +obj-$(CONFIG_TEGRA_IVC) += ivc.o obj-y += lowlevel_init.o ifndef CONFIG_SPL_BUILD obj-$(CONFIG_ARMV7_PSCI) += psci.o diff --git a/arch/arm/mach-tegra/ivc.c b/arch/arm/mach-tegra/ivc.c new file mode 100644 index 00000000000..cf6626fb12c --- /dev/null +++ b/arch/arm/mach-tegra/ivc.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include + +#define TEGRA_IVC_ALIGN 64 + +/* + * IVC channel reset protocol. + * + * Each end uses its tx_channel.state to indicate its synchronization state. + */ +enum ivc_state { + /* + * This value is zero for backwards compatibility with services that + * assume channels to be initially zeroed. Such channels are in an + * initially valid state, but cannot be asynchronously reset, and must + * maintain a valid state at all times. + * + * The transmitting end can enter the established state from the sync or + * ack state when it observes the receiving endpoint in the ack or + * established state, indicating that has cleared the counters in our + * rx_channel. + */ + ivc_state_established = 0, + + /* + * If an endpoint is observed in the sync state, the remote endpoint is + * allowed to clear the counters it owns asynchronously with respect to + * the current endpoint. Therefore, the current endpoint is no longer + * allowed to communicate. + */ + ivc_state_sync, + + /* + * When the transmitting end observes the receiving end in the sync + * state, it can clear the w_count and r_count and transition to the ack + * state. If the remote endpoint observes us in the ack state, it can + * return to the established state once it has cleared its counters. + */ + ivc_state_ack +}; + +/* + * This structure is divided into two-cache aligned parts, the first is only + * written through the tx_channel pointer, while the second is only written + * through the rx_channel pointer. This delineates ownership of the cache lines, + * which is critical to performance and necessary in non-cache coherent + * implementations. + */ +struct tegra_ivc_channel_header { + union { + /* fields owned by the transmitting end */ + struct { + uint32_t w_count; + uint32_t state; + }; + uint8_t w_align[TEGRA_IVC_ALIGN]; + }; + union { + /* fields owned by the receiving end */ + uint32_t r_count; + uint8_t r_align[TEGRA_IVC_ALIGN]; + }; +}; + +static inline void tegra_ivc_invalidate_counter(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *h, + ulong offset) +{ + ulong base = ((ulong)h) + offset; + invalidate_dcache_range(base, base + TEGRA_IVC_ALIGN); +} + +static inline void tegra_ivc_flush_counter(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *h, + ulong offset) +{ + ulong base = ((ulong)h) + offset; + flush_dcache_range(base, base + TEGRA_IVC_ALIGN); +} + +static inline ulong tegra_ivc_frame_addr(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *h, + uint32_t frame) +{ + BUG_ON(frame >= ivc->nframes); + + return ((ulong)h) + sizeof(struct tegra_ivc_channel_header) + + (ivc->frame_size * frame); +} + +static inline void *tegra_ivc_frame_pointer(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *ch, + uint32_t frame) +{ + return (void *)tegra_ivc_frame_addr(ivc, ch, frame); +} + +static inline void tegra_ivc_invalidate_frame(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *h, + unsigned frame) +{ + ulong base = tegra_ivc_frame_addr(ivc, h, frame); + invalidate_dcache_range(base, base + ivc->frame_size); +} + +static inline void tegra_ivc_flush_frame(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *h, + unsigned frame) +{ + ulong base = tegra_ivc_frame_addr(ivc, h, frame); + flush_dcache_range(base, base + ivc->frame_size); +} + +static inline int tegra_ivc_channel_empty(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *ch) +{ + /* + * This function performs multiple checks on the same values with + * security implications, so create snapshots with ACCESS_ONCE() to + * ensure that these checks use the same values. + */ + uint32_t w_count = ACCESS_ONCE(ch->w_count); + uint32_t r_count = ACCESS_ONCE(ch->r_count); + + /* + * Perform an over-full check to prevent denial of service attacks where + * a server could be easily fooled into believing that there's an + * extremely large number of frames ready, since receivers are not + * expected to check for full or over-full conditions. + * + * Although the channel isn't empty, this is an invalid case caused by + * a potentially malicious peer, so returning empty is safer, because it + * gives the impression that the channel has gone silent. + */ + if (w_count - r_count > ivc->nframes) + return 1; + + return w_count == r_count; +} + +static inline int tegra_ivc_channel_full(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *ch) +{ + /* + * Invalid cases where the counters indicate that the queue is over + * capacity also appear full. + */ + return (ACCESS_ONCE(ch->w_count) - ACCESS_ONCE(ch->r_count)) >= + ivc->nframes; +} + +static inline void tegra_ivc_advance_rx(struct tegra_ivc *ivc) +{ + ACCESS_ONCE(ivc->rx_channel->r_count) = + ACCESS_ONCE(ivc->rx_channel->r_count) + 1; + + if (ivc->r_pos == ivc->nframes - 1) + ivc->r_pos = 0; + else + ivc->r_pos++; +} + +static inline void tegra_ivc_advance_tx(struct tegra_ivc *ivc) +{ + ACCESS_ONCE(ivc->tx_channel->w_count) = + ACCESS_ONCE(ivc->tx_channel->w_count) + 1; + + if (ivc->w_pos == ivc->nframes - 1) + ivc->w_pos = 0; + else + ivc->w_pos++; +} + +static inline int tegra_ivc_check_read(struct tegra_ivc *ivc) +{ + ulong offset; + + /* + * tx_channel->state is set locally, so it is not synchronized with + * state from the remote peer. The remote peer cannot reset its + * transmit counters until we've acknowledged its synchronization + * request, so no additional synchronization is required because an + * asynchronous transition of rx_channel->state to ivc_state_ack is not + * allowed. + */ + if (ivc->tx_channel->state != ivc_state_established) + return -ECONNRESET; + + /* + * Avoid unnecessary invalidations when performing repeated accesses to + * an IVC channel by checking the old queue pointers first. + * Synchronization is only necessary when these pointers indicate empty + * or full. + */ + if (!tegra_ivc_channel_empty(ivc, ivc->rx_channel)) + return 0; + + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_invalidate_counter(ivc, ivc->rx_channel, offset); + return tegra_ivc_channel_empty(ivc, ivc->rx_channel) ? -ENOMEM : 0; +} + +static inline int tegra_ivc_check_write(struct tegra_ivc *ivc) +{ + ulong offset; + + if (ivc->tx_channel->state != ivc_state_established) + return -ECONNRESET; + + if (!tegra_ivc_channel_full(ivc, ivc->tx_channel)) + return 0; + + offset = offsetof(struct tegra_ivc_channel_header, r_count); + tegra_ivc_invalidate_counter(ivc, ivc->tx_channel, offset); + return tegra_ivc_channel_full(ivc, ivc->tx_channel) ? -ENOMEM : 0; +} + +static inline uint32_t tegra_ivc_channel_avail_count(struct tegra_ivc *ivc, + struct tegra_ivc_channel_header *ch) +{ + /* + * This function isn't expected to be used in scenarios where an + * over-full situation can lead to denial of service attacks. See the + * comment in tegra_ivc_channel_empty() for an explanation about + * special over-full considerations. + */ + return ACCESS_ONCE(ch->w_count) - ACCESS_ONCE(ch->r_count); +} + +int tegra_ivc_read_get_next_frame(struct tegra_ivc *ivc, void **frame) +{ + int result = tegra_ivc_check_read(ivc); + if (result < 0) + return result; + + /* + * Order observation of w_pos potentially indicating new data before + * data read. + */ + mb(); + + tegra_ivc_invalidate_frame(ivc, ivc->rx_channel, ivc->r_pos); + *frame = tegra_ivc_frame_pointer(ivc, ivc->rx_channel, ivc->r_pos); + + return 0; +} + +int tegra_ivc_read_advance(struct tegra_ivc *ivc) +{ + ulong offset; + int result; + + /* + * No read barriers or synchronization here: the caller is expected to + * have already observed the channel non-empty. This check is just to + * catch programming errors. + */ + result = tegra_ivc_check_read(ivc); + if (result) + return result; + + tegra_ivc_advance_rx(ivc); + offset = offsetof(struct tegra_ivc_channel_header, r_count); + tegra_ivc_flush_counter(ivc, ivc->rx_channel, offset); + + /* + * Ensure our write to r_pos occurs before our read from w_pos. + */ + mb(); + + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_invalidate_counter(ivc, ivc->rx_channel, offset); + + if (tegra_ivc_channel_avail_count(ivc, ivc->rx_channel) == + ivc->nframes - 1) + ivc->notify(ivc); + + return 0; +} + +int tegra_ivc_write_get_next_frame(struct tegra_ivc *ivc, void **frame) +{ + int result = tegra_ivc_check_write(ivc); + if (result) + return result; + + *frame = tegra_ivc_frame_pointer(ivc, ivc->tx_channel, ivc->w_pos); + + return 0; +} + +int tegra_ivc_write_advance(struct tegra_ivc *ivc) +{ + ulong offset; + int result; + + result = tegra_ivc_check_write(ivc); + if (result) + return result; + + tegra_ivc_flush_frame(ivc, ivc->tx_channel, ivc->w_pos); + + /* + * Order any possible stores to the frame before update of w_pos. + */ + mb(); + + tegra_ivc_advance_tx(ivc); + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset); + + /* + * Ensure our write to w_pos occurs before our read from r_pos. + */ + mb(); + + offset = offsetof(struct tegra_ivc_channel_header, r_count); + tegra_ivc_invalidate_counter(ivc, ivc->tx_channel, offset); + + if (tegra_ivc_channel_avail_count(ivc, ivc->tx_channel) == 1) + ivc->notify(ivc); + + return 0; +} + +/* + * =============================================================== + * IVC State Transition Table - see tegra_ivc_channel_notified() + * =============================================================== + * + * local remote action + * ----- ------ ----------------------------------- + * SYNC EST + * SYNC ACK reset counters; move to EST; notify + * SYNC SYNC reset counters; move to ACK; notify + * ACK EST move to EST; notify + * ACK ACK move to EST; notify + * ACK SYNC reset counters; move to ACK; notify + * EST EST + * EST ACK + * EST SYNC reset counters; move to ACK; notify + * + * =============================================================== + */ +int tegra_ivc_channel_notified(struct tegra_ivc *ivc) +{ + ulong offset; + enum ivc_state peer_state; + + /* Copy the receiver's state out of shared memory. */ + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_invalidate_counter(ivc, ivc->rx_channel, offset); + peer_state = ACCESS_ONCE(ivc->rx_channel->state); + + if (peer_state == ivc_state_sync) { + /* + * Order observation of ivc_state_sync before stores clearing + * tx_channel. + */ + mb(); + + /* + * Reset tx_channel counters. The remote end is in the SYNC + * state and won't make progress until we change our state, + * so the counters are not in use at this time. + */ + ivc->tx_channel->w_count = 0; + ivc->rx_channel->r_count = 0; + + ivc->w_pos = 0; + ivc->r_pos = 0; + + /* + * Ensure that counters appear cleared before new state can be + * observed. + */ + mb(); + + /* + * Move to ACK state. We have just cleared our counters, so it + * is now safe for the remote end to start using these values. + */ + ivc->tx_channel->state = ivc_state_ack; + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset); + + /* + * Notify remote end to observe state transition. + */ + ivc->notify(ivc); + } else if (ivc->tx_channel->state == ivc_state_sync && + peer_state == ivc_state_ack) { + /* + * Order observation of ivc_state_sync before stores clearing + * tx_channel. + */ + mb(); + + /* + * Reset tx_channel counters. The remote end is in the ACK + * state and won't make progress until we change our state, + * so the counters are not in use at this time. + */ + ivc->tx_channel->w_count = 0; + ivc->rx_channel->r_count = 0; + + ivc->w_pos = 0; + ivc->r_pos = 0; + + /* + * Ensure that counters appear cleared before new state can be + * observed. + */ + mb(); + + /* + * Move to ESTABLISHED state. We know that the remote end has + * already cleared its counters, so it is safe to start + * writing/reading on this channel. + */ + ivc->tx_channel->state = ivc_state_established; + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset); + + /* + * Notify remote end to observe state transition. + */ + ivc->notify(ivc); + } else if (ivc->tx_channel->state == ivc_state_ack) { + /* + * At this point, we have observed the peer to be in either + * the ACK or ESTABLISHED state. Next, order observation of + * peer state before storing to tx_channel. + */ + mb(); + + /* + * Move to ESTABLISHED state. We know that we have previously + * cleared our counters, and we know that the remote end has + * cleared its counters, so it is safe to start writing/reading + * on this channel. + */ + ivc->tx_channel->state = ivc_state_established; + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset); + + /* + * Notify remote end to observe state transition. + */ + ivc->notify(ivc); + } else { + /* + * There is no need to handle any further action. Either the + * channel is already fully established, or we are waiting for + * the remote end to catch up with our current state. Refer + * to the diagram in "IVC State Transition Table" above. + */ + } + + if (ivc->tx_channel->state != ivc_state_established) + return -EAGAIN; + + return 0; +} + +void tegra_ivc_channel_reset(struct tegra_ivc *ivc) +{ + ulong offset; + + ivc->tx_channel->state = ivc_state_sync; + offset = offsetof(struct tegra_ivc_channel_header, w_count); + tegra_ivc_flush_counter(ivc, ivc->tx_channel, offset); + ivc->notify(ivc); +} + +static int check_ivc_params(ulong qbase1, ulong qbase2, uint32_t nframes, + uint32_t frame_size) +{ + int ret = 0; + + BUG_ON(offsetof(struct tegra_ivc_channel_header, w_count) & + (TEGRA_IVC_ALIGN - 1)); + BUG_ON(offsetof(struct tegra_ivc_channel_header, r_count) & + (TEGRA_IVC_ALIGN - 1)); + BUG_ON(sizeof(struct tegra_ivc_channel_header) & + (TEGRA_IVC_ALIGN - 1)); + + if ((uint64_t)nframes * (uint64_t)frame_size >= 0x100000000) { + error("tegra_ivc: nframes * frame_size overflows\n"); + return -EINVAL; + } + + /* + * The headers must at least be aligned enough for counters + * to be accessed atomically. + */ + if ((qbase1 & (TEGRA_IVC_ALIGN - 1)) || + (qbase2 & (TEGRA_IVC_ALIGN - 1))) { + error("tegra_ivc: channel start not aligned\n"); + return -EINVAL; + } + + if (frame_size & (TEGRA_IVC_ALIGN - 1)) { + error("tegra_ivc: frame size not adequately aligned\n"); + return -EINVAL; + } + + if (qbase1 < qbase2) { + if (qbase1 + frame_size * nframes > qbase2) + ret = -EINVAL; + } else { + if (qbase2 + frame_size * nframes > qbase1) + ret = -EINVAL; + } + + if (ret) { + error("tegra_ivc: queue regions overlap\n"); + return ret; + } + + return 0; +} + +int tegra_ivc_init(struct tegra_ivc *ivc, ulong rx_base, ulong tx_base, + uint32_t nframes, uint32_t frame_size, + void (*notify)(struct tegra_ivc *)) +{ + int ret; + + if (!ivc) + return -EINVAL; + + ret = check_ivc_params(rx_base, tx_base, nframes, frame_size); + if (ret) + return ret; + + ivc->rx_channel = (struct tegra_ivc_channel_header *)rx_base; + ivc->tx_channel = (struct tegra_ivc_channel_header *)tx_base; + ivc->w_pos = 0; + ivc->r_pos = 0; + ivc->nframes = nframes; + ivc->frame_size = frame_size; + ivc->notify = notify; + + return 0; +} -- cgit v1.2.3 From 0e2b5350d9523c9b2dca57b98c89f031691d23e3 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 18 Jul 2016 17:01:50 -0600 Subject: ARM: Add save_boot_params for ARMv8 Implement a hook to allow boards to save boot-time CPU state for later use. When U-Boot is chain-loaded by another bootloader, CPU registers may contain useful information such as system configuration information. This feature mirrors the equivalent ARMv7 feature. Signed-off-by: Stephen Warren Reviewed-by: Tom Rini Signed-off-by: Tom Warren --- arch/arm/cpu/armv8/start.S | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'arch') diff --git a/arch/arm/cpu/armv8/start.S b/arch/arm/cpu/armv8/start.S index 67edf94520c..19c771dba3a 100644 --- a/arch/arm/cpu/armv8/start.S +++ b/arch/arm/cpu/armv8/start.S @@ -53,6 +53,11 @@ _bss_end_ofs: .quad __bss_end - _start reset: + /* Allow the board to save important registers */ + b save_boot_params +.globl save_boot_params_ret +save_boot_params_ret: + #ifdef CONFIG_SYS_RESET_SCTRL bl reset_sctrl #endif @@ -282,3 +287,7 @@ ENTRY(c_runtime_cpu_setup) ret ENDPROC(c_runtime_cpu_setup) + +WEAK(save_boot_params) + b save_boot_params_ret /* back to my caller */ +ENDPROC(save_boot_params) -- cgit v1.2.3 From 2a5f7f20747637cd1f94d4accfd7caa99a7c6035 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Mon, 18 Jul 2016 17:01:51 -0600 Subject: ARM: tegra: pick up actual memory size On Tegra186, U-Boot is booted by the binary firmware as if it were a Linux kernel. Consequently, a DTB is passed to U-Boot. Cache the address of that DTB, and parse the /memory/reg property to determine the actual RAM regions that U-Boot and subsequent EL2/EL1 SW may actually use. Given the binary FW passes a DTB to U-Boot, I anticipate the suggestion that U-Boot use that DTB as its control DTB. I don't believe that would work well, so I do not plan to put any effort into this. By default the FW-supplied DTB is the L4T kernel's DTB, which uses non-upstreamed DT bindings. U-Boot aims to use only upstreamed DT bindings, or as close as it can get. Replacing this DTB with a DTB using upstream bindings is physically quite easy; simply replace the content of one of the GPT partitions on the eMMC. However, the binary FW at least partially relies on the existence/content of some nodes in the DTB, and that requires the DTB to be written according to downstream bindings. Equally, if U-Boot continues to use appended DTBs built from its own source tree, as it does for all other Tegra platforms, development and deployment is much easier. Signed-off-by: Stephen Warren Signed-off-by: Tom Warren --- arch/arm/mach-tegra/board186.c | 12 ---- arch/arm/mach-tegra/tegra186/Makefile | 2 + arch/arm/mach-tegra/tegra186/nvtboot_ll.S | 20 +++++++ arch/arm/mach-tegra/tegra186/nvtboot_mem.c | 88 ++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 arch/arm/mach-tegra/tegra186/nvtboot_ll.S create mode 100644 arch/arm/mach-tegra/tegra186/nvtboot_mem.c (limited to 'arch') diff --git a/arch/arm/mach-tegra/board186.c b/arch/arm/mach-tegra/board186.c index f4b6152a793..876ccba5e59 100644 --- a/arch/arm/mach-tegra/board186.c +++ b/arch/arm/mach-tegra/board186.c @@ -11,12 +11,6 @@ DECLARE_GLOBAL_DATA_PTR; -int dram_init(void) -{ - gd->ram_size = (1.5 * 1024 * 1024 * 1024); - return 0; -} - int board_early_init_f(void) { return 0; @@ -32,12 +26,6 @@ int board_late_init(void) return 0; } -void dram_init_banksize(void) -{ - gd->bd->bi_dram[0].start = CONFIG_SYS_SDRAM_BASE; - gd->bd->bi_dram[0].size = gd->ram_size; -} - void pad_init_mmc(struct mmc_host *host) { } diff --git a/arch/arm/mach-tegra/tegra186/Makefile b/arch/arm/mach-tegra/tegra186/Makefile index 188b097d2c7..033d6005fb4 100644 --- a/arch/arm/mach-tegra/tegra186/Makefile +++ b/arch/arm/mach-tegra/tegra186/Makefile @@ -3,3 +3,5 @@ # SPDX-License-Identifier: GPL-2.0 obj-y += ../board186.o +obj-y += nvtboot_ll.o +obj-y += nvtboot_mem.o diff --git a/arch/arm/mach-tegra/tegra186/nvtboot_ll.S b/arch/arm/mach-tegra/tegra186/nvtboot_ll.S new file mode 100644 index 00000000000..1eab890958c --- /dev/null +++ b/arch/arm/mach-tegra/tegra186/nvtboot_ll.S @@ -0,0 +1,20 @@ +/* + * Save nvtboot-related boot-time CPU state + * + * (C) Copyright 2015-2016 NVIDIA Corporation + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include + +.globl nvtboot_boot_x0 +nvtboot_boot_x0: + .dword 0 + +ENTRY(save_boot_params) + adr x8, nvtboot_boot_x0 + str x0, [x8] + b save_boot_params_ret +ENDPROC(save_boot_params) diff --git a/arch/arm/mach-tegra/tegra186/nvtboot_mem.c b/arch/arm/mach-tegra/tegra186/nvtboot_mem.c new file mode 100644 index 00000000000..37dd8d43348 --- /dev/null +++ b/arch/arm/mach-tegra/tegra186/nvtboot_mem.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +extern unsigned long nvtboot_boot_x0; + +/* + * A parsed version of /memory/reg from the DTB that is passed to U-Boot in x0. + * + * We only support up to two banks since that's all the binary bootloader + * ever sets. We assume bank 0 is RAM below 4G and bank 1 is RAM above 4G. + * This is all a fairly safe assumption, since the L4T kernel makes the same + * assumptions, so the bootloader is unlikely to change. + * + * This is written to before relocation, and hence cannot be in .bss, since + * .bss overlaps the DTB that's appended to the U-Boot binary. The initializer + * forces this into .data and avoids this issue. This also has the nice side- + * effect of the content being valid after relocation. + */ +static struct { + u64 start; + u64 size; +} ram_banks[2] = {{1}}; + +int dram_init(void) +{ + unsigned int na, ns; + const void *nvtboot_blob = (void *)nvtboot_boot_x0; + int node, len, i; + const u32 *prop; + + memset(ram_banks, 0, sizeof(ram_banks)); + + na = fdtdec_get_uint(nvtboot_blob, 0, "#address-cells", 2); + ns = fdtdec_get_uint(nvtboot_blob, 0, "#size-cells", 2); + + node = fdt_path_offset(nvtboot_blob, "/memory"); + if (node < 0) { + error("Can't find /memory node in nvtboot DTB"); + hang(); + } + prop = fdt_getprop(nvtboot_blob, node, "reg", &len); + if (!prop) { + error("Can't find /memory/reg property in nvtboot DTB"); + hang(); + } + + len /= (na + ns); + if (len > ARRAY_SIZE(ram_banks)) + len = ARRAY_SIZE(ram_banks); + + gd->ram_size = 0; + for (i = 0; i < len; i++) { + ram_banks[i].start = of_read_number(prop, na); + prop += na; + ram_banks[i].size = of_read_number(prop, ns); + prop += ns; + gd->ram_size += ram_banks[i].size; + } + + return 0; +} + +extern unsigned long nvtboot_boot_x0; + +void dram_init_banksize(void) +{ + int i; + + for (i = 0; i < 2; i++) { + gd->bd->bi_dram[i].start = ram_banks[i].start; + gd->bd->bi_dram[i].size = ram_banks[i].size; + } +} + +ulong board_get_usable_ram_top(ulong total_size) +{ + return ram_banks[0].start + ram_banks[0].size; +} -- cgit v1.2.3