This patch series adds application level support for host1x hardware on Tegra SoCs. This set of patches can be used in combination with host1x kernel patches. The most recent version of the kernel patch series is available at [0].
An example of using 2d hardware acceleration with this library is available at [1].
Changes in version 2: - Instead of using assertations, the library now returns error codes - Added a minimal set of tests to test common use cases - The size of a stream buffer pool can be set runtime - The library keeps track of syncpoint increments - Added reference counting to buffer management - Removed 2d related patches from the series - Rebased to latest libdrm - Updated ioctl interface - Fixed stylish issues
[0]: http://gitorious.org/linux-host1x/linux-host1x [1]: http://gitorious.org/linux-host1x/libdrm-host1x/commits/2d
Arto Merilainen (2): tegra: Add stream library tests: tegra: Add stream library test
Makefile.am | 6 +- configure.ac | 14 + tegra/Makefile.am | 25 + tegra/class_ids.h | 36 ++ tegra/host1x01_hardware.h | 125 ++++ tegra/hw_host1x01_uclass.h | 155 +++++ tegra/libdrm_tegra.pc.in | 10 + tegra/tegra_drm.c | 998 ++++++++++++++++++++++++++++++++ tegra/tegra_drm.h | 136 +++++ tegra/tegra_drmif.h | 110 ++++ tests/tegra/host1x/Makefile.am | 12 + tests/tegra/host1x/tegra_host1x_test.c | 893 ++++++++++++++++++++++++++++ 12 files changed, 2519 insertions(+), 1 deletion(-) create mode 100644 tegra/Makefile.am create mode 100644 tegra/class_ids.h create mode 100644 tegra/host1x01_hardware.h create mode 100644 tegra/hw_host1x01_uclass.h create mode 100644 tegra/libdrm_tegra.pc.in create mode 100644 tegra/tegra_drm.c create mode 100644 tegra/tegra_drm.h create mode 100644 tegra/tegra_drmif.h create mode 100644 tests/tegra/host1x/Makefile.am create mode 100644 tests/tegra/host1x/tegra_host1x_test.c
This patch introduces tegra stream library. The library is used for buffer management, command stream construction and work synchronization.
Signed-off-by: Arto Merilainen amerilainen@nvidia.com --- Makefile.am | 6 +- configure.ac | 13 + tegra/Makefile.am | 25 ++ tegra/class_ids.h | 36 ++ tegra/host1x01_hardware.h | 125 ++++++ tegra/hw_host1x01_uclass.h | 155 +++++++ tegra/libdrm_tegra.pc.in | 10 + tegra/tegra_drm.c | 998 ++++++++++++++++++++++++++++++++++++++++++++ tegra/tegra_drm.h | 136 ++++++ tegra/tegra_drmif.h | 110 +++++ 10 files changed, 1613 insertions(+), 1 deletion(-) create mode 100644 tegra/Makefile.am create mode 100644 tegra/class_ids.h create mode 100644 tegra/host1x01_hardware.h create mode 100644 tegra/hw_host1x01_uclass.h create mode 100644 tegra/libdrm_tegra.pc.in create mode 100644 tegra/tegra_drm.c create mode 100644 tegra/tegra_drm.h create mode 100644 tegra/tegra_drmif.h
diff --git a/Makefile.am b/Makefile.am index f726036..bd942e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,7 +51,11 @@ if HAVE_FREEDRENO FREEDRENO_SUBDIR = freedreno endif
-SUBDIRS = . $(LIBKMS_SUBDIR) $(INTEL_SUBDIR) $(NOUVEAU_SUBDIR) $(RADEON_SUBDIR) $(OMAP_SUBDIR) $(EXYNOS_SUBDIR) $(FREEDRENO_SUBDIR) tests include man +if HAVE_TEGRA +TEGRA_SUBDIR = tegra +endif + +SUBDIRS = . $(LIBKMS_SUBDIR) $(INTEL_SUBDIR) $(NOUVEAU_SUBDIR) $(RADEON_SUBDIR) $(OMAP_SUBDIR) $(EXYNOS_SUBDIR) $(FREEDRENO_SUBDIR) $(TEGRA_SUBDIR) tests include man
libdrm_la_LTLIBRARIES = libdrm.la libdrm_ladir = $(libdir) diff --git a/configure.ac b/configure.ac index 2786c87..e55e9c1 100644 --- a/configure.ac +++ b/configure.ac @@ -103,6 +103,11 @@ AC_ARG_ENABLE(install-test-programs, [Install test programs (default: no)]), [INSTALL_TESTS=$enableval], [INSTALL_TESTS=no])
+AC_ARG_ENABLE(tegra, + AS_HELP_STRING([--enable-tegra], + [Enable support for tegra's API (default: disabled)]), + [TEGRA=$enableval], [TEGRA=no]) + dnl =========================================================================== dnl check compiler flags AC_DEFUN([LIBDRM_CC_TRY_FLAG], [ @@ -216,6 +221,11 @@ if test "x$FREEDRENO" = xyes; then AC_DEFINE(HAVE_FREEDRENO, 1, [Have freedreno support]) fi
+AM_CONDITIONAL(HAVE_TEGRA, [test "x$TEGRA" = xyes]) +if test "x$TEGRA" = xyes; then + AC_DEFINE(HAVE_TEGRA, 1, [Have TEGRA support]) +fi + AM_CONDITIONAL(HAVE_INSTALL_TESTS, [test "x$INSTALL_TESTS" = xyes]) if test "x$INSTALL_TESTS" = xyes; then AC_DEFINE(HAVE_INSTALL_TESTS, 1, [Install test programs]) @@ -380,6 +390,8 @@ AC_CONFIG_FILES([ exynos/libdrm_exynos.pc freedreno/Makefile freedreno/libdrm_freedreno.pc + tegra/Makefile + tegra/libdrm_tegra.pc tests/Makefile tests/modeprint/Makefile tests/modetest/Makefile @@ -404,4 +416,5 @@ echo " Nouveau API $NOUVEAU" echo " OMAP API $OMAP" echo " EXYNOS API $EXYNOS" echo " Freedreno API $FREEDRENO" +echo " TEGRA API $TEGRA" echo "" diff --git a/tegra/Makefile.am b/tegra/Makefile.am new file mode 100644 index 0000000..72675e5 --- /dev/null +++ b/tegra/Makefile.am @@ -0,0 +1,25 @@ +AM_CFLAGS = \ + $(WARN_CFLAGS) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/tegra \ + $(PTHREADSTUBS_CFLAGS) \ + -I$(top_srcdir)/include/drm + +libdrm_tegra_la_LTLIBRARIES = libdrm_tegra.la +libdrm_tegra_ladir = $(libdir) +libdrm_tegra_la_LDFLAGS = -version-number 1:0:0 -no-undefined +libdrm_tegra_la_LIBADD = ../libdrm.la @PTHREADSTUBS_LIBS@ + +libdrm_tegra_la_SOURCES = \ + tegra_drm.c + +libdrm_tegracommonincludedir = ${includedir}/tegra +libdrm_tegracommoninclude_HEADERS = \ + tegra_drm.h + +libdrm_tegraincludedir = ${includedir}/libdrm +libdrm_tegrainclude_HEADERS = \ + tegra_drmif.h + +pkgconfigdir = @pkgconfigdir@ +pkgconfig_DATA = libdrm_tegra.pc diff --git a/tegra/class_ids.h b/tegra/class_ids.h new file mode 100644 index 0000000..b512306 --- /dev/null +++ b/tegra/class_ids.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012-2013 NVIDIA Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Arto Merilainen amerilainen@nvidia.com + */ + +#ifndef CLASS_IDS_H_ +#define CLASS_IDS_H_ + +enum host1x_class { + HOST1X_CLASS_HOST1X = 0x1, + HOST1X_CLASS_GR2D = 0x51, + HOST1X_CLASS_GR2D_SB = 0x52 +}; + +#endif diff --git a/tegra/host1x01_hardware.h b/tegra/host1x01_hardware.h new file mode 100644 index 0000000..34878e7 --- /dev/null +++ b/tegra/host1x01_hardware.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012-2013 NVIDIA Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Arto Merilainen amerilainen@nvidia.com + */ + +#ifndef HOST1X01_HARDWARE_H_ +#define HOST1X01_HARDWARE_H_ + +#include <linux/types.h> +#include "hw_host1x01_uclass.h" + +/* channel registers */ +#define HOST1X_CHANNEL_MAP_SIZE_BYTES 16384 +#define HOST1X_SYNC_MLOCK_NUM 16 + +/* sync registers */ +#define HOST1X_CHANNEL_SYNC_REG_BASE 0x3000 +#define HOST1X_NB_MLOCKS 16 + +#define BIT(nr) (1UL << (nr)) + +static inline uint32_t host1x_class_host_wait_syncpt(unsigned indx, + unsigned threshold) +{ + return host1x_uclass_wait_syncpt_indx_f(indx) | + host1x_uclass_wait_syncpt_thresh_f(threshold); +} + +static inline uint32_t host1x_class_host_load_syncpt_base(unsigned indx, + unsigned threshold) +{ + return host1x_uclass_load_syncpt_base_base_indx_f(indx) | + host1x_uclass_load_syncpt_base_value_f(threshold); +} + +static inline uint32_t host1x_class_host_wait_syncpt_base(unsigned indx, + unsigned base_indx, + unsigned offset) +{ + return host1x_uclass_wait_syncpt_base_indx_f(indx) | + host1x_uclass_wait_syncpt_base_base_indx_f(base_indx) | + host1x_uclass_wait_syncpt_base_offset_f(offset); +} + +static inline uint32_t host1x_class_host_incr_syncpt_base(unsigned base_indx, + unsigned offset) +{ + return host1x_uclass_incr_syncpt_base_base_indx_f(base_indx) | + host1x_uclass_incr_syncpt_base_offset_f(offset); +} + +static inline uint32_t host1x_class_host_incr_syncpt(unsigned cond, + unsigned indx) +{ + return host1x_uclass_incr_syncpt_cond_f(cond) | + host1x_uclass_incr_syncpt_indx_f(indx); +} + +static inline uint32_t host1x_class_host_indoff_reg_write(unsigned mod_id, + unsigned offset, + int auto_inc) +{ + uint32_t v = host1x_uclass_indoff_indbe_f(0xf) | + host1x_uclass_indoff_indmodid_f(mod_id) | + host1x_uclass_indoff_indroffset_f(offset); + if (auto_inc) + v |= host1x_uclass_indoff_autoinc_f(1); + return v; +} + +static inline uint32_t host1x_class_host_indoff_reg_read(unsigned mod_id, + unsigned offset, + int auto_inc) +{ + uint32_t v = host1x_uclass_indoff_indmodid_f(mod_id) | + host1x_uclass_indoff_indroffset_f(offset) | + host1x_uclass_indoff_rwn_read_v(); + if (auto_inc) + v |= host1x_uclass_indoff_autoinc_f(1); + return v; +} + + +/* cdma opcodes */ +static inline uint32_t host1x_opcode_setclass(unsigned class_id, + unsigned offset, unsigned mask) +{ + return (0 << 28) | (offset << 16) | (class_id << 6) | mask; +} +static inline uint32_t host1x_opcode_nonincr(unsigned offset, unsigned count) +{ + return (2 << 28) | (offset << 16) | count; +} + +static inline uint32_t host1x_opcode_mask(unsigned offset, unsigned mask) +{ + return (3 << 28) | (offset << 16) | mask; +} + +static inline uint32_t host1x_mask2(unsigned x, unsigned y) +{ + return 1 | (1 << (y - x)); +} +#endif diff --git a/tegra/hw_host1x01_uclass.h b/tegra/hw_host1x01_uclass.h new file mode 100644 index 0000000..2237e47 --- /dev/null +++ b/tegra/hw_host1x01_uclass.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2012-2013 NVIDIA Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Arto Merilainen amerilainen@nvidia.com + */ + + /* + * Function naming determines intended use: + * + * <x>_r(void) : Returns the offset for register <x>. + * + * <x>_w(void) : Returns the word offset for word (4 byte) element <x>. + * + * <x>_<y>_s(void) : Returns size of field <y> of register <x> in bits. + * + * <x>_<y>_f(uint32_t v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field <y> of register <x>. This value + * can be |'d with others to produce a full register value for + * register <x>. + * + * <x>_<y>_m(void) : Returns a mask for field <y> of register <x>. This + * value can be ~'d and then &'d to clear the value of field <y> for + * register <x>. + * + * <x>_<y>_<z>_f(void) : Returns the constant value <z> after being shifted + * to place it at field <y> of register <x>. This value can be |'d + * with others to produce a full register value for <x>. + * + * <x>_<y>_v(uint32_t r) : Returns the value of field <y> from a full register + * <x> value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field <y> of register <x>. + * + * <x>_<y>_<z>_v(void) : Returns the constant value for <z> defined for + * field <y> of register <x>. This value is suitable for direct + * comparison with unshifted values appropriate for use in field <y> + * of register <x>. + */ + +#ifndef HW_HOST1X_UCLASS_HOST1X_H_ +#define HW_HOST1X_UCLASS_HOST1X_H_ + +static inline uint32_t host1x_uclass_incr_syncpt_r(void) +{ + return 0x0; +} +static inline uint32_t host1x_uclass_incr_syncpt_cond_f(uint32_t v) +{ + return (v & 0xff) << 8; +} +static inline uint32_t host1x_uclass_incr_syncpt_cond_op_done_v(void) +{ + return 1; +} +static inline uint32_t host1x_uclass_incr_syncpt_indx_f(uint32_t v) +{ + return (v & 0xff) << 0; +} +static inline uint32_t host1x_uclass_wait_syncpt_r(void) +{ + return 0x8; +} +static inline uint32_t host1x_uclass_wait_syncpt_indx_f(uint32_t v) +{ + return (v & 0xff) << 24; +} +static inline uint32_t host1x_uclass_wait_syncpt_thresh_f(uint32_t v) +{ + return (v & 0xffffff) << 0; +} +static inline uint32_t host1x_uclass_wait_syncpt_base_indx_f(uint32_t v) +{ + return (v & 0xff) << 24; +} +static inline uint32_t host1x_uclass_wait_syncpt_base_base_indx_f(uint32_t v) +{ + return (v & 0xff) << 16; +} +static inline uint32_t host1x_uclass_wait_syncpt_base_offset_f(uint32_t v) +{ + return (v & 0xffff) << 0; +} +static inline uint32_t host1x_uclass_wait_syncpt_incr_indx_f(uint32_t v) +{ + return (v & 0xff) << 24; +} +static inline uint32_t host1x_uclass_wait_syncpt_incr_r(void) +{ + return 0x0a; +} +static inline uint32_t host1x_uclass_load_syncpt_base_base_indx_f(uint32_t v) +{ + return (v & 0xff) << 24; +} +static inline uint32_t host1x_uclass_load_syncpt_base_value_f(uint32_t v) +{ + return (v & 0xffffff) << 0; +} +static inline uint32_t host1x_uclass_incr_syncpt_base_base_indx_f(uint32_t v) +{ + return (v & 0xff) << 24; +} +static inline uint32_t host1x_uclass_incr_syncpt_base_offset_f(uint32_t v) +{ + return (v & 0xffffff) << 0; +} +static inline uint32_t host1x_uclass_delay_usec_r(void) +{ + return 0x10; +} +static inline uint32_t host1x_uclass_indoff_r(void) +{ + return 0x2d; +} +static inline uint32_t host1x_uclass_indoff_indbe_f(uint32_t v) +{ + return (v & 0xf) << 28; +} +static inline uint32_t host1x_uclass_indoff_autoinc_f(uint32_t v) +{ + return (v & 0x1) << 27; +} +static inline uint32_t host1x_uclass_indoff_indmodid_f(uint32_t v) +{ + return (v & 0xff) << 18; +} +static inline uint32_t host1x_uclass_indoff_indroffset_f(uint32_t v) +{ + return (v & 0xffff) << 2; +} +static inline uint32_t host1x_uclass_indoff_rwn_read_v(void) +{ + return 1; +} +#endif diff --git a/tegra/libdrm_tegra.pc.in b/tegra/libdrm_tegra.pc.in new file mode 100644 index 0000000..3ad8c6f --- /dev/null +++ b/tegra/libdrm_tegra.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libdrm_tegra +Description: Userspace interface to tegra kernel DRM services +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -ldrm_tegra +Cflags: -I${includedir} -I${includedir}/libdrm -I${includedir}/tegra diff --git a/tegra/tegra_drm.c b/tegra/tegra_drm.c new file mode 100644 index 0000000..05133fe --- /dev/null +++ b/tegra/tegra_drm.c @@ -0,0 +1,998 @@ +/* + * Copyright (C) 2012-2013 NVIDIA Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Arto Merilainen amerilainen@nvidia.com + */ + +#include <linux/errno.h> +#include <linux/types.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include <drm.h> +#include <xf86drm.h> +#include <xf86atomic.h> + +#include "hw_host1x01_uclass.h" +#include "host1x01_hardware.h" +#include "tegra_drmif.h" +#include "tegra_drm.h" +#include "class_ids.h" + +#define TEGRA_SYNCPT_INVALID ((uint32_t)(-1)) + +/* + * Default configuration for new streams + * + * NUMBER_OF_BUFFERS - Determine the number of preallocated command buffers + * RELOC_TABLE_SIZE - Maximum number of memory references in a command buffer + * BUFFER_SIZE_WORDS - Define the size of command buffers + */ + +#define NUMBER_OF_BUFFERS 4 +#define RELOC_TABLE_SIZE 128 +#define BUFFER_SIZE_WORDS 1024 + +enum tegra_stream_status { + TEGRADRM_STREAM_FREE, + TEGRADRM_STREAM_CONSTRUCT, + TEGRADRM_STREAM_READY +}; + +struct tegra_device { + int fd; +}; + +struct tegra_bo { + struct tegra_device *dev; + void *vaddr; + uint32_t gem_handle; + unsigned int offset; + uint32_t size; + atomic_t refcount; +}; + +struct tegra_channel { + struct tegra_device *dev; + uint64_t context; + + enum tegra_module_id module_id; + uint32_t default_class_id; + uint32_t syncpt_id; +}; + +struct tegra_command_buffer { + + struct tegra_bo *mem; + + struct tegra_drm_reloc *reloc_table; + uint32_t *data; + + uint32_t cmd_ptr; + uint32_t reloc_ptr; + uint64_t syncpt_max; + + int flushed; + int preallocated; +}; + +struct tegra_stream { + + enum tegra_stream_status status; + + struct tegra_channel *channel; + int num_words; + int num_relocs; + int num_syncpt_incrs; + + int num_buffers; + int num_max_relocs; + uint32_t buffer_size; + + struct tegra_command_buffer *buffers; + struct tegra_command_buffer *active_buffer; + unsigned int active_buffer_idx; + + uint32_t current_class_id; +}; + +/* + * tegra_next_buffer(stream) + * + * Move to use next command buffer. NOTE! This routine does not verify that the + * new buffer is ready to use. + */ + +static void tegra_next_buffer(struct tegra_stream *stream) +{ + stream->active_buffer_idx = + (stream->active_buffer_idx + 1) % stream->num_buffers; + stream->active_buffer = &stream->buffers[stream->active_buffer_idx]; +} + +/* + * tegra_release_cmdbuf(buffer) + * + * This function releases given command buffer. + */ + +static void tegra_release_cmdbuf(struct tegra_command_buffer *buffer) +{ + free(buffer->reloc_table); + tegra_bo_free(buffer->mem); + memset(buffer, 0, sizeof(*buffer)); +} + +/* + * tegra_allocate_cmdbuf(buffer) + * + * This function allocates and initializes a command buffer. cmduf + * structure must be zeroed before calling this function! + */ + +static int tegra_allocate_cmdbuf(struct tegra_device *dev, + struct tegra_command_buffer *buffer, + uint32_t buffer_size, uint32_t num_relocs) +{ + + /* Allocate and map memory for opcodes */ + + if (!(buffer->mem = + tegra_bo_allocate(dev, sizeof(uint32_t) * buffer_size, 4))) + goto err_buffer_create; + + if(!(buffer->data = tegra_bo_map(buffer->mem))) + goto err_buffer_create; + + /* Allocate reloc_table */ + if (!(buffer->reloc_table = + malloc(num_relocs * sizeof(struct tegra_drm_reloc)))) + goto err_buffer_create; + + /* Initialize rest of the struct */ + buffer->reloc_ptr = 0; + buffer->cmd_ptr = 0; + + return 0; + +err_buffer_create: + tegra_release_cmdbuf(buffer); + return -ENOMEM; +} + +/* + * tegra_device_create(fd) + * + * Create a device "object" representing tegra drm device. The device should be + * opened using i.e. drmOpen(). If object cannot be created, NULL is returned + */ + +struct tegra_device *tegra_device_create(int fd) +{ + struct tegra_device *dev; + + if (!(dev = malloc(sizeof(dev)))) + goto err_dev_alloc; + dev->fd = fd; + + return dev; + +err_dev_alloc: + return NULL; +} + +/* + * tegra_device_destroy(dev) + * + * Remove device object created using tegra_device_create(). The caller is + * responsible for calling drmClose(). + */ + +void tegra_device_destroy(struct tegra_device *dev) +{ + if (!dev) + return; + free(dev); +} + +/* + * tegra_channel_open(dev, module_id) + * + * Reserve channel resources for given module. Host1x has several channels + * each of which is dedicated for a certain hardware module. The opened + * channel is used by streams for delivering command buffers. + */ + +struct tegra_channel *tegra_channel_open(struct tegra_device *dev, + enum tegra_module_id module_id) +{ + struct tegra_channel *channel; + struct tegra_drm_get_syncpt get_args; + struct tegra_drm_open_channel open_args; + uint32_t default_class_id; + + if (!(channel = malloc(sizeof(*channel)))) + goto err_channel_alloc; + + switch (module_id) { + case TEGRADRM_MODULEID_2D: + default_class_id = HOST1X_CLASS_GR2D; + break; + default: + return NULL; + } + + channel->dev = dev; + channel->module_id = module_id; + channel->default_class_id = default_class_id; + + /* Open the channel */ + open_args.client = default_class_id; + if (drmIoctl(dev->fd, DRM_IOCTL_TEGRA_OPEN_CHANNEL, &open_args)) + goto err_channel_open; + channel->context = open_args.context; + + /* Get a syncpoint for the channel */ + get_args.context = open_args.context; + get_args.index = 0; + if (drmIoctl(dev->fd, DRM_IOCTL_TEGRA_GET_SYNCPT, &get_args)) + goto err_tegra_ioctl; + channel->syncpt_id = get_args.id; + + return channel; + +err_tegra_ioctl: + drmIoctl(dev->fd, DRM_IOCTL_TEGRA_CLOSE_CHANNEL, &open_args); +err_channel_open: + free(channel); +err_channel_alloc: + return NULL; +} + +/* + * tegra_channel_close(channel) + * + * Close a channel. + */ + +void tegra_channel_close(struct tegra_channel *channel) +{ + struct tegra_drm_close_channel close_args; + + if (!channel) + return; + + close_args.context = channel->context; + drmIoctl(channel->dev->fd, DRM_IOCTL_TEGRA_CLOSE_CHANNEL, &close_args); + + free(channel); +} + + +/* + * tegra_stream_create(channel) + * + * Create a stream for given channel. This function preallocates several + * command buffers for later usage to improve performance. Streams are + * used for generating command buffers opcode by opcode using + * tegra_stream_push(). + */ + +struct tegra_stream *tegra_stream_create(struct tegra_channel *channel, + uint32_t buffer_size, + int num_buffers, int num_max_relocs) +{ + struct tegra_stream *stream; + int i; + + if (!channel) + goto err_bad_channel; + + if (!(stream = malloc(sizeof(*stream)))) + goto err_alloc_stream; + + memset(stream, 0, sizeof(*stream)); + stream->channel = channel; + stream->status = TEGRADRM_STREAM_FREE; + + stream->buffer_size = buffer_size ? buffer_size : BUFFER_SIZE_WORDS; + stream->num_buffers = num_buffers ? num_buffers : NUMBER_OF_BUFFERS; + stream->num_max_relocs = + num_max_relocs ? num_max_relocs : RELOC_TABLE_SIZE; + + if (!(stream->buffers = + malloc(sizeof(struct tegra_command_buffer) * stream->num_buffers))) + goto err_alloc_cmdbufs; + + /* tegra_allocate_cmdbuf() assumes buffer elements to be zeroed */ + memset(stream->buffers, 0, + sizeof(struct tegra_command_buffer) * stream->num_buffers); + + for (i = 0; i < stream->num_buffers; i++) { + if (tegra_allocate_cmdbuf(channel->dev, &stream->buffers[i], + stream->buffer_size, stream->num_max_relocs)) + goto err_buffer_create; + + stream->buffers[i].preallocated = 1; + } + + stream->active_buffer_idx = 0; + stream->active_buffer = &stream->buffers[0]; + + return stream; + +err_buffer_create: + for (i = 0; i < stream->num_buffers; i++) + tegra_release_cmdbuf(&stream->buffers[i]); + free(stream->buffers); +err_alloc_cmdbufs: + free(stream); +err_alloc_stream: +err_bad_channel: + return NULL; +} + +/* + * tegra_stream_destroy(stream) + * + * Destroy the given stream object. All resrouces are released. + */ + +void tegra_stream_destroy(struct tegra_stream *stream) +{ + int i; + + if (!stream) + return; + + for (i = 0; i < stream->num_buffers; i++) { + free(stream->buffers[i].reloc_table); + tegra_bo_free(stream->buffers[i].mem); + } + + free(stream->buffers); + free(stream); +} + +/* + * tegra_fence_is_valid(fence) + * + * Check validity of a fence. + */ + +int tegra_fence_is_valid(const struct tegra_fence *fence) +{ + int valid = 1; + valid = valid && fence ? 1 : 0; + valid = valid && fence->id != TEGRA_SYNCPT_INVALID; + return valid; +} + +/* + * tegra_fence_clear(fence) + * + * Clear (=invalidate) given fence + */ + +void tegra_fence_clear(struct tegra_fence *fence) +{ + fence->id = TEGRA_SYNCPT_INVALID; + fence->value = 0; +} + +/* + * tegra_fence_copy(dst, src) + * + * Copy fence + */ + +void tegra_fence_copy(struct tegra_fence *dst, const struct tegra_fence *src) +{ + *dst = *src; +} + +/* + * tegra_fence_waitex(channel, fence, timeout, value) + * + * Wait for a given syncpoint value with timeout. The end value is returned in + * "value" variable. The function returns 0 if the syncpoint value was + * reached before timeout, otherwise an error code. + */ + +int tegra_fence_waitex(struct tegra_channel *channel, + struct tegra_fence *fence, long timeout, long *value) +{ + struct tegra_drm_syncpt_wait args; + int err; + + if (!tegra_fence_is_valid(fence)) + return -EINVAL; + + args.timeout = timeout; + args.id = fence->id; + args.thresh = fence->value; + args.value = 0; + + err = drmIoctl(channel->dev->fd, DRM_IOCTL_TEGRA_SYNCPT_WAIT, &args); + + if (value) + *value = args.value; + + return err; +} + +/* + * tegra_fence_wait_timeout(channel, fence, timeout) + * + * Wait for a given syncpoint value with timeout. The function returns 0 if + * the syncpoint value was reached before timeout, otherwise an error code. + */ + +int tegra_fence_wait_timeout(struct tegra_channel *channel, + struct tegra_fence *fence, long timeout) +{ + return tegra_fence_waitex(channel, fence, timeout, NULL); +} + +/* + * tegra_fence_wait(channel, wait) + * + * Wait for a given syncpoint value without timeout. + */ + +int tegra_fence_wait(struct tegra_channel *channel, struct tegra_fence *fence) +{ + return tegra_fence_waitex(channel, fence, DRM_TEGRA_NO_TIMEOUT, NULL); +} + +/* + * tegra_stream_push_reloc(stream, h, offset) + * + * Push a memory reference to the stream. + */ + +int tegra_stream_push_reloc(struct tegra_stream *stream, struct tegra_bo *h, + int offset) +{ + struct tegra_drm_reloc reloc; + + if (!(stream && h && stream->num_words >= 1 && stream->num_relocs >= 1)) + return -EINVAL; + + reloc.cmdbuf.handle = stream->active_buffer->mem->gem_handle; + reloc.cmdbuf.offset = stream->active_buffer->cmd_ptr * 4; + reloc.target.handle = h->gem_handle; + reloc.target.offset = offset; + reloc.shift = 0; + + stream->num_words--; + stream->num_relocs--; + stream->active_buffer->data[stream->active_buffer->cmd_ptr++] = + 0xDEADBEEF; + stream->active_buffer->reloc_table[stream->active_buffer->reloc_ptr++] = + reloc; + + return 0; +} + +/* + * tegra_bo_gethandle(h) + * + * Get drm memory handle. This is required if the object is used as a + * framebuffer. + */ + +uint32_t tegra_bo_gethandle(struct tegra_bo *h) +{ + return h->gem_handle; +} + +/* + * tegra_bo_allocate(dev, num_bytes, alignment) + * + * Allocate num_bytes for host1x device operations. The memory is not + * automatically mapped for the application. + */ + +struct tegra_bo *tegra_bo_allocate(struct tegra_device *dev, + uint32_t num_bytes, uint32_t alignment) +{ + struct tegra_drm_gem_create create; + struct tegra_bo *h; + + if (!(h = malloc(sizeof(*h)))) + goto err_alloc_memory_handle; + + /* Allocate memory */ + memset(&create, 0, sizeof(create)); + create.size = num_bytes; + if (drmIoctl(dev->fd, DRM_IOCTL_TEGRA_GEM_CREATE, &create)) + goto err_alloc_memory; + + h->gem_handle = create.handle; + h->size = create.size; + h->offset = 0; + h->vaddr = NULL; + h->dev = dev; + atomic_set(&h->refcount, 1); + + return h; + +err_alloc_memory: + free(h); +err_alloc_memory_handle: + return NULL; +} + +/* + * tegra_bo_free(h) + * + * Release given memory handle. Memory is unmapped if it is mapped. Kernel + * takes care of reference counting, so the memory area will not be freed + * unless the kernel actually has finished using the area. + */ + +void tegra_bo_free(struct tegra_bo * h) +{ + struct drm_gem_close unref; + + if (!h) + return; + + tegra_bo_unmap(h); + unref.handle = h->gem_handle; + drmIoctl(h->dev->fd, DRM_IOCTL_GEM_CLOSE, &unref); + free(h); +} + +/* + * tegra_bo_get(h) + * + * Increase reference counting to the given bo handle + */ + +void tegra_bo_get(struct tegra_bo *h) +{ + if (!h) + return; + atomic_inc(&h->refcount); +} + +/* + * tegra_bo_put(h) + * + * Decrease reference counting to the given bo handle. The buffer is freed + * if all references to the buffer object are dropped. + */ + +void tegra_bo_put(struct tegra_bo *h) +{ + if (!h) + return; + if (atomic_dec_and_test(&h->refcount)) + tegra_bo_free(h); +} +/* + * tegra_bo_map(h) + * + * Map the given handle for the application. + */ + +void * tegra_bo_map(struct tegra_bo * h) +{ + if (!h->offset) { + struct tegra_drm_gem_mmap args = {h->gem_handle, 0}; + int ret; + + ret = drmIoctl(h->dev->fd, DRM_IOCTL_TEGRA_GEM_MMAP, &args); + if (ret) + return NULL; + h->offset = args.offset; + } + + if (!h->vaddr) + h->vaddr = mmap(NULL, h->size, PROT_READ | PROT_WRITE, + MAP_SHARED, h->dev->fd, h->offset); + + return h->vaddr; +} + +/* + * tegra_bo_unmap(h) + * + * Unmap memory from the application. The contents of the memory region is + * automatically flushed to the memory + */ + +void tegra_bo_unmap(struct tegra_bo * h) +{ + if (!(h && h->vaddr)) + return; + + munmap(h->vaddr, h->size); + h->vaddr = NULL; +} + +/* + * tegra_stream_flush(stream, fence) + * + * Send the current contents of stream buffer. The stream must be + * synchronized correctly (we cannot send partial streams). If + * pointer to fence is given, the fence will contain the syncpoint value + * that is reached when operations in the buffer are finished. + */ + +int tegra_stream_flush(struct tegra_stream *stream, struct tegra_fence *fence) +{ + struct tegra_channel *ch = stream->channel; + struct tegra_drm_cmdbuf cmdbuf; + struct tegra_drm_submit submit; + struct tegra_drm_syncpt syncpt_incr; + struct tegra_command_buffer * buffer = stream->active_buffer; + int err; + + if (!stream) + return -EINVAL; + + /* Reflushing is fine */ + if (stream->status == TEGRADRM_STREAM_FREE) + return 0; + + /* Return error if stream is constructed badly */ + if(stream->status != TEGRADRM_STREAM_READY) + return -EINVAL; + + /* Clean args */ + memset(&submit, 0, sizeof(submit)); + + /* Construct cmd buffer */ + cmdbuf.handle = buffer->mem->gem_handle; + cmdbuf.offset = 0; + cmdbuf.words = buffer->cmd_ptr; + + /* Construct syncpoint increments struct */ + syncpt_incr.id = ch->syncpt_id; + syncpt_incr.incrs = stream->num_syncpt_incrs; + + /* Create submit */ + submit.context = ch->context; + submit.num_relocs = buffer->reloc_ptr; + submit.num_syncpts = 1; + submit.num_cmdbufs = 1; + submit.relocs = (uint32_t)buffer->reloc_table; + submit.syncpts = (uint32_t)&syncpt_incr; + submit.cmdbufs = (uint32_t)&cmdbuf; + + /* Push submits to the channel */ + if ((err = drmIoctl(ch->dev->fd, DRM_IOCTL_TEGRA_SUBMIT, &submit))) { + tegra_fence_clear(fence); + return err; + } + + /* Return fence */ + if (fence) { + fence->id = ch->syncpt_id; + fence->value = submit.fence; + } + + stream->num_syncpt_incrs = 0; + stream->active_buffer->syncpt_max = submit.fence; + stream->active_buffer->flushed = 1; + + /* Release non-preallocated buffers */ + if (!stream->active_buffer->preallocated) { + tegra_release_cmdbuf(stream->active_buffer); + free(stream->active_buffer); + + /* Restore the original active buffer */ + stream->active_buffer = + &stream->buffers[stream->active_buffer_idx]; + } + + stream->status = TEGRADRM_STREAM_FREE; + return 0; +} + +/* + * tegra_stream_begin(stream, num_words, fence, num_fences, num_syncpt_incrs, + * num_relocs, class_id) + * + * Start constructing a stream. + * - num_words refer to the maximum number of words the stream can contain. + * - fence is a pointer to a table that contains syncpoint preconditions + * before the stream execution can start. + * - num_fences indicate the number of elements in the fence table. + * - num_relocs indicate the number of memory references in the buffer. + * - class_id refers to the class_id that is selected in the beginning of a + * stream. If no class id is given, the default class id (=usually the + * client device's class) is selected. + * + * This function verifies that the current buffer has enough room for holding + * the whole stream (this is computed using num_words and num_relocs). The + * function blocks until the stream buffer is ready for use. + */ + +int tegra_stream_begin(struct tegra_stream *stream, int num_words, + struct tegra_fence *fence, int num_fences, + int num_relocs, uint32_t class_id) +{ + int i; + + /* check stream and its state */ + if (!(stream && (stream->status == TEGRADRM_STREAM_FREE || + stream->status == TEGRADRM_STREAM_READY))) + return -EINVAL; + + /* check fence validity */ + for (i = 0; i < num_fences; i++) + if(!tegra_fence_is_valid(fence + i)) + return -EINVAL; + + /* handle class id */ + if (!class_id && stream->channel->default_class_id) + class_id = stream->channel->default_class_id; + + /* include following in num words: + * - fence waits in the beginningi ( 1 + num_fences) + * - setclass in the beginning (1 word) + * - syncpoint increment at the end of the stream (2 words) + */ + + num_words += 2; + num_words += class_id ? 1 : 0; + num_words += num_fences ? 1 + num_fences : 0; + + /* Flush, if the current buffer is full */ + + if ((stream->active_buffer->cmd_ptr + num_words + num_relocs > + stream->buffer_size) || + (stream->active_buffer->reloc_ptr + num_relocs > + (uint32_t)stream->num_max_relocs)) + tegra_stream_flush(stream, NULL); + + /* Check if we cannot use a preallocated buffer */ + + if ((uint32_t)(num_words + num_relocs) > stream->buffer_size || + num_relocs > stream->num_max_relocs) { + struct tegra_command_buffer *cmdbuf; + + /* The new stream does not fit into a preallocated buffer. + * Allocate a new buffer */ + + if (!(cmdbuf = malloc(sizeof(*cmdbuf)))) + return -ENOMEM; + memset(cmdbuf, 0, sizeof(*cmdbuf)); + + if (tegra_allocate_cmdbuf(stream->channel->dev, cmdbuf, + num_words, num_relocs)) { + free(cmdbuf); + return -ENOMEM; + } + + stream->active_buffer = cmdbuf; + + } else if (stream->active_buffer->flushed) { + + /* We can use preallocated buffer. Make sure the buffer is + * actually free */ + + struct tegra_fence fence; + + tegra_next_buffer(stream); + + fence.id = stream->channel->syncpt_id; + fence.value = stream->active_buffer->syncpt_max; + tegra_fence_wait(stream->channel, &fence); + + stream->active_buffer->cmd_ptr = 0; + stream->active_buffer->reloc_ptr = 0; + stream->active_buffer->flushed = 0; + } + + stream->status = TEGRADRM_STREAM_CONSTRUCT; + stream->current_class_id = class_id; + stream->num_relocs = num_relocs; + stream->num_words = num_words; + + /* Add fences */ + if (num_fences) { + tegra_stream_push(stream, + host1x_opcode_setclass(HOST1X_CLASS_HOST1X, + host1x_uclass_wait_syncpt_r(), num_fences)); + + for (i = 0; i < num_fences; i++) + tegra_stream_push(stream, + host1x_class_host_wait_syncpt( + fence[i].id, fence[i].value)); + } + + if (class_id) + tegra_stream_push(stream, + host1x_opcode_setclass(class_id, 0, 0)); + + return 0; +} + +/* + * tegra_stream_push_incr(stream, cond) + * + * Push "increment syncpt" opcode to the stream. This function maintains + * the counter of pushed increments + */ + +int tegra_stream_push_incr(struct tegra_stream *stream, uint32_t cond) +{ + if (!(stream && stream->num_words >= 2 && + stream->status == TEGRADRM_STREAM_CONSTRUCT)) + return -EINVAL; + + /* Add syncpoint increment on cond */ + tegra_stream_push(stream, host1x_opcode_nonincr( + host1x_uclass_incr_syncpt_r(), 1)); + tegra_stream_push(stream, host1x_class_host_incr_syncpt( + cond, stream->channel->syncpt_id)); + + stream->num_syncpt_incrs += 1; + + return 0; +} + +/* + * tegra_stream_push_setclass(stream, class_id) + * + * Push "set class" opcode to the stream. Do nothing if the class is already + * active + */ + +int tegra_stream_push_setclass(struct tegra_stream *stream, uint32_t class_id) +{ + if (!(stream && stream->num_words >= 1 && + stream->status == TEGRADRM_STREAM_CONSTRUCT)) + return -EINVAL; + + if (stream->current_class_id == class_id) + return 0; + + tegra_stream_push(stream, host1x_opcode_setclass(class_id, 0, 0)); + + stream->current_class_id = class_id; + return 0; +} + +/* + * tegra_stream_end(stream) + * + * Mark end of stream. This function pushes last syncpoint increment for + * marking end of stream. + */ + +int tegra_stream_end(struct tegra_stream *stream) +{ + if (!(stream && stream->status == TEGRADRM_STREAM_CONSTRUCT && + stream->num_words >= 2)) + return -EINVAL; + + /* Add last syncpoint increment on OP_DONE */ + tegra_stream_push_incr(stream, + host1x_uclass_incr_syncpt_cond_op_done_v()); + + stream->status = TEGRADRM_STREAM_READY; + return 0; +} + +/* + * tegra_stream_push(stream, word) + * + * Push a single word to given stream. + */ + +int tegra_stream_push(struct tegra_stream *stream, uint32_t word) +{ + if (!(stream && stream->num_words >= 1 && + stream->status == TEGRADRM_STREAM_CONSTRUCT)) + return -EINVAL; + + stream->num_words--; + stream->active_buffer->data[stream->active_buffer->cmd_ptr++] = word; + + return 0; +} + +/* + * tegra_reloc (variable, handle, offset) + * + * This function creates a reloc allocation. The function should be used in + * conjunction with tegra_stream_push_words. + */ + +struct tegra_reloc tegra_reloc(const void *var, const struct tegra_bo *h, + const uint32_t offset) +{ + struct tegra_reloc reloc = {var, (struct tegra_bo *)h, offset}; + return reloc; + +} + +/* + * tegra_stream_push_words(stream, addr, words, ...) + * + * Push words from given address to stream. The function takes + * reloc structs as its argument. You can generate the structs with tegra_reloc + * function. + */ + +int tegra_stream_push_words(struct tegra_stream *stream, const void *addr, + int words, int num_relocs, int num_syncpt_incrs, + ...) +{ + va_list ap; + struct tegra_reloc reloc_arg; + struct tegra_command_buffer *buffer; + + if (!(stream && stream->num_words >= words && + stream->num_relocs >= num_relocs)) + return -EINVAL; + + buffer = stream->active_buffer; + + stream->num_words -= words; + stream->num_relocs -= num_relocs; + stream->num_syncpt_incrs += num_syncpt_incrs; + + /* Copy the contents */ + memcpy(buffer->data + buffer->cmd_ptr, addr, words * sizeof(uint32_t)); + + /* Copy relocs */ + va_start(ap, num_syncpt_incrs); + for (; num_relocs; num_relocs--) { + + uint32_t cmd_ptr; + struct tegra_drm_reloc reloc_entry; + + reloc_arg = va_arg(ap, struct tegra_reloc); + + cmd_ptr = buffer->cmd_ptr + + ((uint32_t *) reloc_arg.addr) - ((uint32_t *) addr); + + reloc_entry.cmdbuf.handle = buffer->mem->gem_handle; + reloc_entry.cmdbuf.offset = cmd_ptr * 4; + reloc_entry.target.handle = reloc_arg.h->gem_handle; + reloc_entry.target.offset = reloc_arg.offset; + reloc_entry.shift = 0; + + buffer->data[cmd_ptr] = 0xDEADBEEF; + buffer->reloc_table[buffer->reloc_ptr++] = reloc_entry; + } + va_end(ap); + + buffer->cmd_ptr += words; + + return 0; +} diff --git a/tegra/tegra_drm.h b/tegra/tegra_drm.h new file mode 100644 index 0000000..d1fe337 --- /dev/null +++ b/tegra/tegra_drm.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#ifndef TEGRA_DRM_H_ +#define TEGRA_DRM_H_ + +struct tegra_drm_gem_create { + __u64 size; + __u32 flags; + __u32 handle; +}; + +struct tegra_drm_gem_mmap { + __u32 handle; + __u32 offset; +}; + +struct tegra_drm_syncpt_read { + __u32 id; + __u32 value; +}; + +struct tegra_drm_syncpt_incr { + __u32 id; + __u32 pad; +}; + +struct tegra_drm_syncpt_wait { + __u32 id; + __u32 thresh; + __u32 timeout; + __u32 value; +}; + +#define DRM_TEGRA_NO_TIMEOUT (0xffffffff) + +struct tegra_drm_open_channel { + __u32 client; + __u32 pad; + __u64 context; +}; + +struct tegra_drm_close_channel { + __u64 context; +}; + +struct tegra_drm_get_syncpt { + __u64 context; + __u32 index; + __u32 id; +}; + +struct tegra_drm_syncpt { + __u32 id; + __u32 incrs; +}; + +struct tegra_drm_cmdbuf { + __u32 handle; + __u32 offset; + __u32 words; + __u32 pad; +}; + +struct tegra_drm_reloc { + struct { + __u32 handle; + __u32 offset; + } cmdbuf; + struct { + __u32 handle; + __u32 offset; + } target; + __u32 shift; + __u32 pad; +}; + +struct tegra_drm_waitchk { + __u32 handle; + __u32 offset; + __u32 syncpt; + __u32 thresh; +}; + +struct tegra_drm_submit { + __u64 context; + __u32 num_syncpts; + __u32 num_cmdbufs; + __u32 num_relocs; + __u32 num_waitchks; + __u32 waitchk_mask; + __u32 timeout; + __u32 pad; + __u64 syncpts; + __u64 cmdbufs; + __u64 relocs; + __u64 waitchks; + __u32 fence; /* Return value */ + + __u32 reserved[5]; /* future expansion */ +}; + +#define DRM_TEGRA_GEM_CREATE 0x00 +#define DRM_TEGRA_GEM_MMAP 0x01 +#define DRM_TEGRA_SYNCPT_READ 0x02 +#define DRM_TEGRA_SYNCPT_INCR 0x03 +#define DRM_TEGRA_SYNCPT_WAIT 0x04 +#define DRM_TEGRA_OPEN_CHANNEL 0x05 +#define DRM_TEGRA_CLOSE_CHANNEL 0x06 +#define DRM_TEGRA_GET_SYNCPT 0x07 +#define DRM_TEGRA_SUBMIT 0x08 + +#define DRM_IOCTL_TEGRA_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GEM_CREATE, struct tegra_drm_gem_create) +#define DRM_IOCTL_TEGRA_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GEM_MMAP, struct tegra_drm_gem_mmap) +#define DRM_IOCTL_TEGRA_SYNCPT_READ DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_SYNCPT_READ, struct tegra_drm_syncpt_read) +#define DRM_IOCTL_TEGRA_SYNCPT_INCR DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_SYNCPT_INCR, struct tegra_drm_syncpt_incr) +#define DRM_IOCTL_TEGRA_SYNCPT_WAIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_SYNCPT_WAIT, struct tegra_drm_syncpt_wait) +#define DRM_IOCTL_TEGRA_OPEN_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_OPEN_CHANNEL, struct tegra_drm_open_channel) +#define DRM_IOCTL_TEGRA_CLOSE_CHANNEL DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_CLOSE_CHANNEL, struct tegra_drm_open_channel) +#define DRM_IOCTL_TEGRA_GET_SYNCPT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_GET_SYNCPT, struct tegra_drm_get_syncpt) +#define DRM_IOCTL_TEGRA_SUBMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_TEGRA_SUBMIT, struct tegra_drm_submit) + +#endif diff --git a/tegra/tegra_drmif.h b/tegra/tegra_drmif.h new file mode 100644 index 0000000..198696b --- /dev/null +++ b/tegra/tegra_drmif.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012-2013 NVIDIA Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Arto Merilainen amerilainen@nvidia.com + */ + +#ifndef TEGRA_DRMIF_H_ +#define TEGRA_DRMIF_H_ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct tegra_channel; +struct tegra_bo; +struct tegra_stream; +struct tegra_device; + +struct tegra_fence { + uint32_t id; + uint32_t value; +}; + +struct tegra_reloc { + const void *addr; + struct tegra_bo *h; + uint32_t offset; +}; + +enum tegra_module_id { + TEGRADRM_MODULEID_2D +}; + +/* Device operations */ +struct tegra_device *tegra_device_create(int fd); +void tegra_device_destroy(struct tegra_device *dev); + +/* Memory operations */ +uint32_t tegra_bo_gethandle(struct tegra_bo *handle); +struct tegra_bo *tegra_bo_allocate(struct tegra_device *dev, + uint32_t num_bytes, uint32_t alignment); +void tegra_bo_free(struct tegra_bo * handle); +void * tegra_bo_map(struct tegra_bo * handle); +void tegra_bo_unmap(struct tegra_bo * handle); +void tegra_bo_get(struct tegra_bo *handle); +void tegra_bo_put(struct tegra_bo *handle); + +/* Channel operations */ +struct tegra_channel *tegra_channel_open(struct tegra_device *dev, + enum tegra_module_id module_id); +void tegra_channel_close(struct tegra_channel *channel); + +/* Stream operations */ +struct tegra_stream *tegra_stream_create(struct tegra_channel *channel, + uint32_t buffer_size, + int num_buffers, int num_max_relocs); +void tegra_stream_destroy(struct tegra_stream *stream); +int tegra_stream_begin(struct tegra_stream *stream, int num_words, + struct tegra_fence *fence, int num_fences, + int num_relocs, uint32_t class_id); +int tegra_stream_end(struct tegra_stream *stream); +int tegra_stream_flush(struct tegra_stream *stream, struct tegra_fence *fence); +int tegra_stream_push(struct tegra_stream *stream, uint32_t word); +int tegra_stream_push_incr(struct tegra_stream *stream, uint32_t cond); +int tegra_stream_push_setclass(struct tegra_stream *stream, uint32_t class_id); +int tegra_stream_push_reloc(struct tegra_stream *stream, + struct tegra_bo *handle, int offset); +struct tegra_reloc tegra_reloc(const void *var, const struct tegra_bo *handle, + const uint32_t offset); +int tegra_stream_push_words(struct tegra_stream *stream, const void *addr, + int words, int num_relocs, int num_syncpt_incrs, + ...); + +/* Fence operations */ +int tegra_fence_wait(struct tegra_channel *channel, struct tegra_fence *fence); +int tegra_fence_wait_timeout(struct tegra_channel *channel, + struct tegra_fence *fence, long timeout); +int tegra_fence_waitex(struct tegra_channel *channel, + struct tegra_fence *fence, long timeout, long *value); +int tegra_fence_is_valid(const struct tegra_fence *fence); +void tegra_fence_clear(struct tegra_fence *fence); +void tegra_fence_copy(struct tegra_fence *dst, const struct tegra_fence *src); + +#ifdef __cplusplus +}; +#endif + +#endif
This patch adds a minimal test set for the stream library and host1x kernel interface.
The test verifies that the driver (or library) is able to: - Increment, read and wait for syncpoint values - Use a host1x channel to do host1x operations - Handle submit timeout correctly - Do relocations to buffer - Allocate and release memory - Use stream pools correctly
Signed-off-by: Arto Merilainen amerilainen@nvidia.com --- configure.ac | 1 + tests/tegra/host1x/Makefile.am | 12 + tests/tegra/host1x/tegra_host1x_test.c | 893 ++++++++++++++++++++++++++++++++ 3 files changed, 906 insertions(+) create mode 100644 tests/tegra/host1x/Makefile.am create mode 100644 tests/tegra/host1x/tegra_host1x_test.c
diff --git a/configure.ac b/configure.ac index e55e9c1..a678bcd 100644 --- a/configure.ac +++ b/configure.ac @@ -399,6 +399,7 @@ AC_CONFIG_FILES([ tests/radeon/Makefile tests/vbltest/Makefile tests/exynos/Makefile + tests/tegra/host1x/Makefile include/Makefile include/drm/Makefile man/Makefile diff --git a/tests/tegra/host1x/Makefile.am b/tests/tegra/host1x/Makefile.am new file mode 100644 index 0000000..700f764 --- /dev/null +++ b/tests/tegra/host1x/Makefile.am @@ -0,0 +1,12 @@ +AM_CFLAGS = \ + -I $(top_srcdir)/include/drm \ + -I $(top_srcdir) + +LDADD = \ + $(top_builddir)/libdrm.la + +noinst_PROGRAMS = \ + tegra_host1x_test + +tegra_host1x_test_SOURCES = \ + tegra_host1x_test.c diff --git a/tests/tegra/host1x/tegra_host1x_test.c b/tests/tegra/host1x/tegra_host1x_test.c new file mode 100644 index 0000000..548f422 --- /dev/null +++ b/tests/tegra/host1x/tegra_host1x_test.c @@ -0,0 +1,893 @@ +/* + * Copyright (C) 2012-2013 NVIDIA Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Arto Merilainen amerilainen@nvidia.com + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +/* Include the code file to access the internals of the library */ +#include "tegra/tegra_drm.c" + +#define HOST1X_OPCODE_NOP host1x_opcode_nonincr(0, 0) + +/* + * test_oversized_submit(channel) - Do a submit that does not fit into + * preallocated stream buffer + */ + +int test_oversized_submit(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + unsigned int diff_ms; + int i; + + /* Create a really small buffer */ + if (!(stream = tegra_stream_create(channel, 4, 0, 0))) + return -1; + + if (tegra_stream_begin(stream, 100, NULL, 0, 0, + HOST1X_CLASS_HOST1X)) + goto destroy; + for (i = 0; i < 100; ++i) { + if (tegra_stream_push(stream, HOST1X_OPCODE_NOP)) + goto destroy; + } + if (tegra_stream_end(stream)) + goto destroy; + if (tegra_stream_flush(stream, &fence)) + goto destroy; + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_stream_destroy(stream); + return -1; + +} + +/* + * test_huge_submit(channel) - Do single huge submit and wait for completion + */ + +int test_huge_submit(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + const unsigned int submit_count = 1000; + struct timespec tp_begin, tp_end; + unsigned int diff_ms; + int i; + + clock_gettime(CLOCK_MONOTONIC, &tp_begin); + + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + return -1; + + /* Create many small submits */ + for (i = 0; i < submit_count; ++i) { + if (tegra_stream_begin(stream, 1, NULL, 0, 0, + HOST1X_CLASS_HOST1X)) + goto destroy; + if (tegra_stream_push(stream, HOST1X_OPCODE_NOP)) + goto destroy; + if (tegra_stream_end(stream)) + goto destroy; + } + + /* Flush all at the same time */ + if (tegra_stream_flush(stream, &fence)) + goto destroy; + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + clock_gettime(CLOCK_MONOTONIC, &tp_end); + diff_ms = (tp_end.tv_sec - tp_begin.tv_sec) * 1000 + + (tp_end.tv_nsec - tp_begin.tv_nsec) / 1000000; + + printf("Doing %u iterations in a single submit took %ums\n", + submit_count, diff_ms); + + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_stream_destroy(stream); + return -1; + +} + +/* + * test_many_small_submits(channel) - Do several small submits and wait for + * completion + */ + +int test_many_small_submits(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + const unsigned int submit_count = 1000; + struct timespec tp_begin, tp_end; + unsigned int diff_ms; + int i; + + clock_gettime(CLOCK_MONOTONIC, &tp_begin); + + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + return -1; + + /* Create many small submits */ + for (i = 0; i < submit_count; ++i) { + if (tegra_stream_begin(stream, 1, NULL, 0, 0, + HOST1X_CLASS_HOST1X)) + goto destroy; + + if (tegra_stream_push(stream, HOST1X_OPCODE_NOP)) + goto destroy; + + if (tegra_stream_end(stream)) + goto destroy; + + /* Flush each submit separately */ + if (tegra_stream_flush(stream, &fence)) + goto destroy; + } + + if (!tegra_fence_is_valid(&fence)) + goto destroy; + + /* Wait until complete */ + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + clock_gettime(CLOCK_MONOTONIC, &tp_end); + diff_ms = (tp_end.tv_sec - tp_begin.tv_sec) * 1000 + + (tp_end.tv_nsec - tp_begin.tv_nsec) / 1000000; + + printf("Doing %u individual submits took %ums\n", submit_count, + diff_ms); + + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_stream_destroy(stream); + return -1; + +} + +/* + * test_wait_current_value(channel) - Test waiting the current value with 0 + * timeout + */ + +int test_wait_current_value(struct tegra_channel *channel) +{ + struct tegra_drm_syncpt_read read_args = {channel->syncpt_id, 0}; + struct tegra_fence fence = {channel->syncpt_id, 0}; + int fd = channel->dev->fd; + int err = 0; + + if (err = drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_READ, &read_args)) + goto exit; + fence.value = read_args.value + 1; + err = tegra_fence_waitex(channel, &fence, 0, NULL); +exit: + return err; +} + +/* + * test_wait_future_value(channel) - Test waiting future value with 0 + * timeout + */ + +int test_wait_future_value(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + const unsigned int id = channel->syncpt_id; + const unsigned int delay_len = 15; + struct tegra_drm_syncpt_incr incr_args = {channel->syncpt_id, 0}; + int fd = channel->dev->fd; + int i; + + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + return -1; + + /* Wait for a syncpoint increment */ + if (tegra_stream_begin(stream, 2, NULL, 0, 0, HOST1X_CLASS_HOST1X)) + goto destroy; + if (tegra_stream_push(stream, host1x_opcode_nonincr( + host1x_uclass_wait_syncpt_incr_r(), 1))) + goto destroy; + if (tegra_stream_push(stream, + host1x_uclass_wait_syncpt_incr_indx_f(id))) + goto destroy; + + /* Tell the library that we're doing a lot of increments */ + stream->num_syncpt_incrs++; + + if (tegra_stream_end(stream)) + goto destroy; + + /* flush and validate fence */ + if (tegra_stream_flush(stream, &fence)) + goto destroy; + if (!tegra_fence_is_valid(&fence)) + goto destroy; + + /* reading a future value should return an error */ + if (!tegra_fence_waitex(channel, &fence, 0, NULL)) + goto destroy; + + /* let the host continue */ + if (drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_INCR, &incr_args)) + goto destroy_wait_timeout; + + /* wait for the end of submit */ + if (tegra_fence_waitex(channel, &fence, 1000, NULL)) + goto destroy; + + tegra_stream_destroy(stream); + return 0; + +destroy_wait_timeout: + tegra_fence_waitex(channel, &fence, 15000, NULL); +destroy: + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_bad_increment(channel) - Try doing a bad increment + */ + +int test_bad_increment(struct tegra_channel *channel) +{ + int fd = channel->dev->fd; + const unsigned int id = channel->syncpt_id; + struct tegra_drm_syncpt_incr incr_args = {channel->syncpt_id, 0}; + struct tegra_drm_syncpt_read read_args = {channel->syncpt_id, 0}; + unsigned int value_0, value_1; + int err = 0; + + /* Read syncpoint value in the beginning */ + if ((err = drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_READ, &read_args))) + goto exit; + value_0 = read_args.value; + + /* Try doing an increment (it should not pass) */ + if (!drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_INCR, &incr_args)) { + err = -1; + goto exit; + } + + /* Read the new syncpoint value */ + if ((err = drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_READ, &read_args))) + goto exit; + value_1 = read_args.value; + + /* Validate that the kernel did not do any increments */ + if (value_1 != value_0) + err = -1; +exit: + return err; +} + +int test_host_incr(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + int i; + + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + return -1; + + if (tegra_stream_begin(stream, 2, NULL, 0, 0, HOST1X_CLASS_HOST1X)) + goto destroy; + if (tegra_stream_push_incr(stream, 0)) + goto destroy; + if (tegra_stream_end(stream)) + goto destroy; + + if (tegra_stream_flush(stream, &fence)) + goto destroy; + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_host_wait(channel) - Make host wait for cpu increments + */ + +int test_host_wait(struct tegra_channel *channel) +{ + int fd = channel->dev->fd; + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + const unsigned int syncpt_incrs = 15; + const unsigned int id = channel->syncpt_id; + struct tegra_drm_syncpt_incr incr_args = {channel->syncpt_id, 0}; + struct tegra_drm_syncpt_read read_args = {channel->syncpt_id, 0}; + unsigned int value_0, value_1; + int i; + + /* + * Stream construction + */ + + /* Create stream */ + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + return -1; + + /* Start constructing a cmd buffer */ + if (tegra_stream_begin(stream, 1 + syncpt_incrs, NULL, 0, 0, + HOST1X_CLASS_HOST1X)) + goto destroy; + + /* Wait for syncpoint increments */ + if (tegra_stream_push(stream, host1x_opcode_nonincr( + host1x_uclass_wait_syncpt_incr_r(), syncpt_incrs))) + goto destroy; + for (i = 0; i < syncpt_incrs; ++i) + if (tegra_stream_push(stream, + host1x_uclass_wait_syncpt_incr_indx_f(id))) + goto destroy; + + /* Tell the library that we're doing a lot of increments */ + stream->num_syncpt_incrs += syncpt_incrs; + + /* End and flush */ + if (tegra_stream_end(stream)) + goto destroy; + if (tegra_stream_flush(stream, &fence)) + goto destroy; + if (!tegra_fence_is_valid(&fence)) + goto destroy; + + /* + * Act as a client for host1x (i.e. do syncpoint increments) + */ + + /* Read syncpoint value in the beginning */ + if (drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_READ, &read_args)) + goto destroy_wait_timeout; + value_0 = read_args.value; + + /* Do increments */ + for (i = 0; i < syncpt_incrs; ++i) + if (drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_INCR, &incr_args)) + goto destroy_wait_timeout; + + /* The kernel should return immdiately */ + if (tegra_fence_waitex(channel, &fence, 100, NULL)) + goto destroy; + + /* Read the new syncpoint value */ + if (drmIoctl(fd, DRM_IOCTL_TEGRA_SYNCPT_READ, &read_args)) + goto destroy; + value_1 = read_args.value; + + /* Validate that increments were made correctly */ + if (value_1 != value_0 + syncpt_incrs + 1) + goto destroy; + + tegra_stream_destroy(stream); + return 0; + +destroy_wait_timeout: + tegra_fence_waitex(channel, &fence, 15000, NULL); +destroy: + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_pool(channel) - Test stream pool + * + * The stream library supports pooling of streams. This means that there is + * a small pool of preallocated stream buffers. If these buffers run out, + * the library waits until one is released. This routine tests that we + * actually can access the preallocated buffers immediately and if no + * buffers are available, we actually wait until one is free. + */ + +int test_pool(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + struct timespec tp_curr, tp_prev; + const unsigned int pool_size = 3; + int i; + + if (!(stream = tegra_stream_create(channel, 0, pool_size, 0))) + return -1; + + for (i = 0; i < pool_size * 2; ++i) { + unsigned int diff_ms; + + /* measure how long it takes to begin a stream */ + clock_gettime(CLOCK_MONOTONIC, &tp_prev); + if (tegra_stream_begin(stream, 3, NULL, 0, 0, + HOST1X_CLASS_HOST1X)) + goto destroy; + clock_gettime(CLOCK_MONOTONIC, &tp_curr); + diff_ms = (tp_curr.tv_sec - tp_prev.tv_sec) * 1000 + + (tp_curr.tv_nsec - tp_prev.tv_nsec) / 1000000; + + /* it should not be too much as long as we have buffers + * available */ + if (diff_ms > 500 && i < pool_size) + goto destroy; + + /* ..and it should be quite large if no buffers are available + * because the library needs to wait for a free buffer */ + if (diff_ms < 500 && i >= pool_size) + goto destroy; + + /* Make host1x wait 1 sec */ + if (tegra_stream_push(stream, host1x_opcode_nonincr( + host1x_uclass_delay_usec_r(), 1))) + goto destroy; + if (tegra_stream_push(stream, 0xFFFFF)) + goto destroy; + + /* End and flush */ + if (tegra_stream_end(stream)) + goto destroy; + if (tegra_stream_flush(stream, &fence)) + goto destroy; + } + + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_push_words(channel) - Test push_words -API + */ + +int test_push_words(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + struct tegra_bo *bo = NULL; + uint32_t *reloc_ptr, reloc_entry_val; + struct { + uint32_t nonincr; + uint32_t reloc; + } words; + + /* Allocate memory for a relocation */ + if (!(bo = tegra_bo_allocate(channel->dev, 1, 4))) + goto destroy; + + /* Create and begin a stream */ + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + goto destroy; + if (tegra_stream_begin(stream, 2, NULL, 0, 1, HOST1X_CLASS_GR2D)) + goto destroy; + + /* Push data to dstba register */ + words.nonincr = host1x_opcode_nonincr(0x2b, 1); + + /* Push reloc. Store the temporary value from the library */ + if (tegra_stream_push_words(stream, &words, 2, 1, 0, + tegra_reloc(&words.reloc, bo, 0))) + goto destroy; + reloc_ptr = + &stream->active_buffer->data[stream->active_buffer->cmd_ptr - 1]; + reloc_entry_val = *reloc_ptr; + + /* end stream */ + if (tegra_stream_end(stream)) + goto destroy; + + /* push the command buffer to the kernel */ + if (tegra_stream_flush(stream, &fence)) + goto destroy; + + /* the kernel should have patched the memory address */ + if (reloc_entry_val == *reloc_ptr) + goto destroy; + + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_reloc_bad_register(channel) - Test relocation to a non-address + * register + */ + +int test_reloc_bad_register(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + struct tegra_bo *bo = NULL; + + /* Allocate memory for a relocation */ + if (!(bo = tegra_bo_allocate(channel->dev, 4096, 4))) + goto destroy; + + /* Create and begin a stream */ + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + goto destroy; + if (tegra_stream_begin(stream, 2, NULL, 0, 1, HOST1X_CLASS_HOST1X)) + goto destroy; + + /* push data to syncpoint increment register */ + if (tegra_stream_push(stream, host1x_opcode_nonincr( + host1x_uclass_incr_syncpt_r(), 1))) + goto destroy; + + /* Push reloc. */ + if (tegra_stream_push_reloc(stream, bo, 0)) + goto destroy; + + /* end stream */ + if (tegra_stream_end(stream)) + goto destroy; + + /* push the command buffer to the kernel. this should fail */ + if (!tegra_stream_flush(stream, &fence)) + goto destroy; + + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_reloc_bad_offset(channel) - Test relocation with a bad offset + */ + +int test_reloc_bad_offset(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + struct tegra_bo *bo = NULL; + + /* Allocate memory for a relocation */ + if (!(bo = tegra_bo_allocate(channel->dev, 4096, 4))) + goto destroy; + + /* Create and begin a stream */ + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + goto destroy; + if (tegra_stream_begin(stream, 2, NULL, 0, 1, HOST1X_CLASS_GR2D)) + goto destroy; + + /* push data to dstba register */ + if (tegra_stream_push(stream, host1x_opcode_nonincr(0x2b, 1))) + goto destroy; + + /* Push reloc with bad offset */ + if (tegra_stream_push_reloc(stream, bo, 0x100000)) + goto destroy; + + /* end stream */ + if (tegra_stream_end(stream)) + goto destroy; + + /* push the command buffer to the kernel. this should fail */ + if (!tegra_stream_flush(stream, &fence)) + goto destroy; + + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_reloc(channel) - Test relocations + */ + +int test_reloc(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + struct tegra_bo *bo = NULL; + uint32_t *reloc_ptr, reloc_entry_val; + + /* Allocate memory for a relocation */ + if (!(bo = tegra_bo_allocate(channel->dev, 1, 4))) + goto destroy; + + /* Create and begin a stream */ + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + goto destroy; + if (tegra_stream_begin(stream, 2, NULL, 0, 1, HOST1X_CLASS_GR2D)) + goto destroy; + + /* push data to dstba register */ + if (tegra_stream_push(stream, host1x_opcode_nonincr(0x2b, 1))) + goto destroy; + + /* Push reloc. Store the temporary value from the library */ + reloc_ptr = + &stream->active_buffer->data[stream->active_buffer->cmd_ptr]; + if (tegra_stream_push_reloc(stream, bo, 0)) + goto destroy; + reloc_entry_val = *reloc_ptr; + + /* end stream */ + if (tegra_stream_end(stream)) + goto destroy; + + /* push the command buffer to the kernel */ + if (tegra_stream_flush(stream, &fence)) + goto destroy; + + /* the kernel should have patched the memory address */ + if (reloc_entry_val == *reloc_ptr) + goto destroy; + + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_bo_free(bo); + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_timeout(channel) - Test that the kernel survives from timeouts + */ + +int test_timeout(struct tegra_channel *channel) +{ + struct tegra_stream *stream = NULL; + struct tegra_fence fence; + const unsigned int delay_len = 15; + int i; + + if (!(stream = tegra_stream_create(channel, 0, 0, 0))) + return -1; + + if (tegra_stream_begin(stream, 1 + delay_len, NULL, 0, 0, + HOST1X_CLASS_HOST1X)) + goto destroy; + + /* make the host wait ~15 seconds. This should trigger the timeout */ + if (tegra_stream_push(stream, host1x_opcode_nonincr( + host1x_uclass_delay_usec_r(), delay_len))) + goto destroy; + for (i = 0; i < delay_len; ++i) + if (tegra_stream_push(stream, 0xFFFFF)) + goto destroy; + + if (tegra_stream_end(stream)) + goto destroy; + if (tegra_stream_flush(stream, &fence)) + goto destroy; + if (!tegra_fence_is_valid(&fence)) + goto destroy; + if (tegra_fence_waitex(channel, &fence, 15000, NULL)) + goto destroy; + + tegra_stream_destroy(stream); + return 0; + +destroy: + tegra_stream_destroy(stream); + return -1; +} + +/* + * test_drain(channel) - Allocate, use and release memory. Check that we can do + * that again. + */ + +int test_bo_drain(struct tegra_channel *channel) +{ + const unsigned int alloc_size = (1024 * 1024); + const unsigned int num_allocs = 1024; + const unsigned int num_trials = 5; + + unsigned int first_time_blocks_allocated = 0; + int i, j, err = 0; + + for (i = 0; i < num_trials; ++i) { + struct tegra_bo *bos[num_allocs]; + unsigned int blocks_allocated; + + for (j = 0; j < num_allocs; ++j) { + bos[j] = tegra_bo_allocate(channel->dev, alloc_size, + 4); + if (!bos[j]) + break; + if (!tegra_bo_map(bos[j])) + break; + memset(bos[j]->vaddr, j % 256, alloc_size); + } + + blocks_allocated = j; + + while (j-- > 0) { + int k = 0; + for (k = 0; k < alloc_size; ++k) + if (((char *)bos[j]->vaddr)[k] != (j % 256)) + err = -1; + + /* Test put/get for every 3th allocation */ + if (!(j % 3)) { + tegra_bo_get(bos[j]); + tegra_bo_put(bos[j]); + } + + /* Test that both free and put release the memory */ + if (!(j % 2)) + tegra_bo_free(bos[j]); + else + tegra_bo_put(bos[j]); + } + + if (!first_time_blocks_allocated) + first_time_blocks_allocated = blocks_allocated; + else if (first_time_blocks_allocated != blocks_allocated) { + err = -1; + break; + } + } + return err; +} + +struct test_data { + const char *name; + int (*func)(struct tegra_channel *channel); + int known_failure; +}; + +#define TEST(test_name) {#test_name, test_name, 0} +#define FAILING_TEST(test_name) {#test_name, test_name, 1} + +struct test_data tests[] = { + FAILING_TEST(test_bad_increment), + TEST(test_wait_current_value), + FAILING_TEST(test_wait_future_value), + TEST(test_host_wait), + TEST(test_many_small_submits), + TEST(test_huge_submit), + TEST(test_oversized_submit), + TEST(test_bo_drain), + TEST(test_timeout), + TEST(test_reloc), + TEST(test_reloc_bad_register), + FAILING_TEST(test_reloc_bad_offset), + TEST(test_push_words), + TEST(test_pool), + TEST(test_host_incr) +}; +const unsigned int num_tests = sizeof(tests) / sizeof(*tests); + +int main(int argc, char *argv[]) +{ + struct tegra_device *dev; + struct tegra_channel *channel; + int fd, i, num_unknown_failures = 0, num_failures = 0; + + fd = drmOpen("tegra", NULL); + if (fd < 0) { + printf("Failed to open tegra device!\n"); + goto err_drm_open; + } + + if (!(dev = tegra_device_create(fd))) { + printf("Failed to create tegra device!\n"); + goto err_tegra_device_create; + } + + if (!(channel = tegra_channel_open(dev, TEGRADRM_MODULEID_2D))) { + printf("Failed to open 2d channel!\n"); + goto err_tegra_channel_open; + } + + for (i = 0; i < num_tests; ++i) { + int err = tests[i].func(channel); + + printf("%s: %s\n", tests[i].name, err ? "fail" : "pass"); + + num_failures += err ? 1 : 0; + num_unknown_failures += + (err && !tests[i].known_failure) ? 1 : 0; + } + + printf("\nFailed %d/%d tests\n", num_failures, num_tests); + if (num_unknown_failures) + printf("FAILED\n"); + else if (num_failures) + printf("PASSED with known failures\n"); + else + printf("PASSED\n"); + + tegra_channel_close(channel); + tegra_device_destroy(dev); + close(fd); + + return num_unknown_failures ? -1 : 0; + +err_tegra_channel_open: + tegra_device_destroy(dev); +err_tegra_device_create: + close(fd); +err_drm_open: + return -1; +}
dri-devel@lists.freedesktop.org