From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 85CF3556B92; Tue, 8 Sep 2026 14:56:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788879439; cv=none; b=nRSRphkLrzbXlIJDG+pzLiq8j0egdIbcgWvY34S7QxlOGSl6Lk5YrEsqfyXxJml6yHZc23xfw1QANP9EuL99lYmuvxdrBY7Uqtv4DaybMPawXolRlFrQUOE0/XW6Eh9YToFPnNSCaogbNbem52O5kqCPTHg2HRRC3DFyRz8BOhI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788879439; c=relaxed/simple; bh=vKMKCEDdAYarTrmrgK3jqlmNYdYYBvHrf5nqWrqEcNE=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=sccafudA/pPqoXDG/wU5Yzcn8/iNFGH5nnDziTZxAN/w7/LKcGkQ0iBxkyaPa+cuwYffFaufXIDqFi3tPOBKItC/b9dVnBK7fO1JCVyBkapV5plVKNLQ6NkJUg58prNkjN8iRH0NJnxL47jriZ0wX9dJZMzmOktz9N82qcUkLTs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XVgvf/WS; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="XVgvf/WS" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A774B1F00A3A; Tue, 8 Sep 2026 14:56:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1788879413; bh=fAe5fvsbqD2XkxTpdhGDvAqOH3I9qvvIECQlzlHp6NI=; h=Date:From:To:Cc:Subject:References:In-Reply-To; b=XVgvf/WSz0wIlsN9W5llcoLv7Jeou2aHZT0r1kYKpBXBTKvfT7+GyVezxSJZLdk8y QS2ctOaWFllrEmYCvTBauiVfCj4crehWZnZXnpvh73fAi8sr20YlKpMkZ1vzQf0RqK KJk2MvpCuOM1GFPa9mjVQsbbkU49bQXOPjopHkSX8yP+dl1Awtv5zXzoyjiqbezTTb FcxyLhHYlN3h3fuBGAYQ6WomzqQzbZMgkLJmmAtrWao1Ksrbbt9XAUtl4o0O/u7YQD dGOjpmNo2IvAZGLgB9pPKA0F6cPOM9wWeMfMOqxjciApeDK8y9UcWj7oxsGm7pWJRe 4WEkdIpuGz63A== Date: Tue, 8 Sep 2026 09:56:47 -0500 From: Rob Herring To: Thierry Reding Cc: Krzysztof Kozlowski , Conor Dooley , Jonathan Hunter , David Airlie , Simona Vetter , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , Sowjanya Komatineni , Luca Ceresoli , Mikko Perttunen , Yury Norov , Rasmus Villemoes , Russell King , Alexander Gordeev , Gerald Schaefer , Heiko Carstens , Vasily Gorbik , Christian Borntraeger , Sven Schnelle , Andrew Morton , David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko , Marek Szyprowski , Robin Murphy , Sumit Semwal , Benjamin Gaignard , Brian Starkey , John Stultz , "T.J. Mercier" , Christian =?iso-8859-1?Q?K=F6nig?= , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Catalin Marinas , Will Deacon , Chun Ng , Mark Rutland , Saravana Kannan , Thierry Reding , devicetree@vger.kernel.org, linux-tegra@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-s390@vger.kernel.org, linux-mm@kvack.org, iommu@lists.linux.dev, linaro-mm-sig@lists.linaro.org, linux-trace-kernel@vger.kernel.org, Thierry Reding Subject: Re: [PATCH v6 09/12] dma-buf: heaps: Add support for Tegra VPR Message-ID: <20260908145647.GA3123582-robh@kernel.org> References: <20260904-tegra-vpr-v6-0-79042cfa8de5@nvidia.com> <20260904-tegra-vpr-v6-9-79042cfa8de5@nvidia.com> Precedence: bulk X-Mailing-List: linux-trace-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20260904-tegra-vpr-v6-9-79042cfa8de5@nvidia.com> On Fri, Sep 04, 2026 at 12:45:00PM +0200, Thierry Reding wrote: > From: Thierry Reding > > NVIDIA Tegra SoCs commonly define a Video-Protection-Region, which is a > region of memory dedicated to content-protected video decode and > playback. This memory cannot be accessed by the CPU and only certain > hardware devices have access to it. > > Expose the VPR as a DMA heap so that applications and drivers can > allocate buffers from this region for use-cases that require this kind > of protected memory. > > VPR has a few very critical peculiarities. First, it must be a single > contiguous region of memory (there is a single pair of registers that > set the base address and size of the region), which is configured by > calling back into the secure monitor. The memory region also needs to > quite large for some use-cases because it needs to fit multiple video > frames (8K video should be supported), so VPR sizes of ~2 GiB are > expected. However, some devices cannot afford to reserve this amount > of memory for a particular use-case, and therefore the VPR must be > resizable. > > Unfortunately, resizing the VPR is slightly tricky because the GPU found > on Tegra SoCs must be in reset during the VPR resize operation. This is > currently implemented by freezing all userspace processes and calling > invoking the GPU's freeze() implementation, resizing and the thawing the > GPU and userspace processes. This is quite heavy-handed, so eventually > it might be better to implement thawing/freezing in the GPU driver in > such a way that they block accesses to the GPU so that the VPR resize > operation can happen without suspending all userspace. > > In order to balance the memory usage versus the amount of resizing that > needs to happen, the VPR is divided into multiple chunks. Each chunk is > implemented as a section of the CMA area that is completely allocated on > first use to guarantee the contiguity of the VPR. Once all buffers from > a chunk have been freed, the subsection is freed and the memory returned > to the system. > > The Tegra VPR driver is split into two pieces: one tiny part of the > driver is always built-in and sets up the CMA area during early boot, > whereas the second, larger, part provides the DMA heap implementation > and can be built as a module. Note that tearing down VPR is tricky and > usually not necessary, so it can currently not be unloaded. > > Signed-off-by: Thierry Reding > --- > Changes in v6: > - split code into a small core and the main chunk so the latter can be > built as a module > > Changes in v5: > - use newly introduced cma_alloc_at() and work with a single CMA area > - setup CMA early and initialize VPR later during boot > - remove some unused variables > - use kalloc_objs() > > Changes in v4: > - address Sashiko and checkpatch comments > - fully remove from linear map while chunks are allocated > - improve error handling > - remove freezer support > > Changes in v3: > - use set_memory_device() and set_memory_normal() helpers > - use kzalloc_obj() instead of kzalloc() with sizeof() > > Changes in v2: > - cluster allocations to reduce the number of resize operations > - support cross-chunk allocation > --- > drivers/dma-buf/heaps/Kconfig | 12 + > drivers/dma-buf/heaps/Makefile | 10 + > drivers/dma-buf/heaps/tegra-vpr-init.c | 133 ++++ > drivers/dma-buf/heaps/tegra-vpr.c | 1210 ++++++++++++++++++++++++++++++++ > drivers/dma-buf/heaps/tegra-vpr.h | 73 ++ > include/trace/events/tegra_vpr.h | 57 ++ > 6 files changed, 1495 insertions(+) > > diff --git a/drivers/dma-buf/heaps/Kconfig b/drivers/dma-buf/heaps/Kconfig > index bb729e91545c..28d2c0800fb5 100644 > --- a/drivers/dma-buf/heaps/Kconfig > +++ b/drivers/dma-buf/heaps/Kconfig > @@ -20,3 +20,15 @@ config DMABUF_HEAPS_CMA > Choose this option to enable dma-buf CMA heap. This heap is backed > by the Contiguous Memory Allocator (CMA). If your system has these > regions, you should say Y here. > + > +config DMABUF_HEAPS_TEGRA_VPR > + tristate "NVIDIA Tegra Video-Protected-Region DMA-BUF Heap" > + depends on DMABUF_HEAPS && DMA_CMA > + help > + Choose this option to enable Video-Protected-Region (VPR) support on > + a range of NVIDIA Tegra devices. Access to VPR memory is limited to > + a subset of hardware engines and specifically disallowed from the > + CPU. The region can be fixed, in which case no linear mapping exists > + for the memory, or it can be resizable on systems that want to reuse > + the memory for other uses when content-protected video is not played > + back. > diff --git a/drivers/dma-buf/heaps/Makefile b/drivers/dma-buf/heaps/Makefile > index 974467791032..481fcb78f757 100644 > --- a/drivers/dma-buf/heaps/Makefile > +++ b/drivers/dma-buf/heaps/Makefile > @@ -1,3 +1,13 @@ > # SPDX-License-Identifier: GPL-2.0 > obj-$(CONFIG_DMABUF_HEAPS_SYSTEM) += system_heap.o > obj-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o > + > +# > +# The reserved-memory bits always need to be built-in so that the CMA > +# initialization runs during early boot. The VPR driver itself can be > +# built as a module. > +# > +ifneq ($(CONFIG_DMABUF_HEAPS_TEGRA_VPR),) > +obj-y += tegra-vpr-init.o > +obj-$(CONFIG_DMABUF_HEAPS_TEGRA_VPR) += tegra-vpr.o > +endif > diff --git a/drivers/dma-buf/heaps/tegra-vpr-init.c b/drivers/dma-buf/heaps/tegra-vpr-init.c > new file mode 100644 > index 000000000000..65e917713c6d > --- /dev/null > +++ b/drivers/dma-buf/heaps/tegra-vpr-init.c > @@ -0,0 +1,133 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * DMA-BUF restricted heap exporter for NVIDIA Video-Protection-Region (VPR) > + * > + * Copyright (C) 2024-2026 NVIDIA Corporation > + */ > + > +#define pr_fmt(fmt) "tegra-vpr: " fmt > + > +#include "tegra-vpr.h" > + > +#include > +#include > + > +static DEFINE_MUTEX(vpr_lock); > +static LIST_HEAD_GUARDED(vpr_list, vpr_lock); > + > +static int __init tegra_vpr_node_init(unsigned long offset, > + struct reserved_mem *rmem) > +{ > + struct cma *cma; > + int err; > + > + if (!IS_ALIGNED(rmem->base, SZ_1M)) { > + pr_err("%s: base is not aligned to 1 MiB\n", rmem->name); > + return -EINVAL; > + } > + > + if (!IS_ALIGNED(rmem->size, SZ_1M)) { > + pr_err("%s: size is not aligned to 1 MiB\n", rmem->name); > + return -EINVAL; > + } > + > + err = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name, > + &cma); > + if (err < 0) { > + pr_err("%s: failed to initialize CMA: %d\n", __func__, err); > + return err; > + } > + > + rmem->priv = cma; > + > + return 0; > +} > + > +static struct tegra_vpr *tegra_vpr_lookup(struct cma *cma) > +{ > + struct tegra_vpr *vpr; > + > + mutex_lock(&vpr_lock); > + > + list_for_each_entry(vpr, &vpr_list, list) { > + if (vpr->cma == cma) { > + mutex_unlock(&vpr_lock); > + return vpr; > + } > + } > + > + mutex_unlock(&vpr_lock); > + > + return ERR_PTR(-EPROBE_DEFER); > +} > + > +static int tegra_vpr_device_init(struct reserved_mem *rmem, struct device *dev) > +{ > + const struct dev_pm_ops *pm = dev->driver->pm; > + struct tegra_vpr_device *node; > + struct cma *cma = rmem->priv; > + struct tegra_vpr *vpr; > + > + vpr = tegra_vpr_lookup(cma); > + if (IS_ERR(vpr)) > + return PTR_ERR(vpr); > + > + if (!pm || !pm->freeze || !pm->thaw) > + return -EINVAL; > + > + node = kzalloc_obj(*node, GFP_KERNEL); > + if (!node) > + return -ENOMEM; > + > + INIT_LIST_HEAD(&node->node); > + node->dev = dev; > + > + mutex_lock(&vpr->lock); > + list_add_tail(&node->node, &vpr->devices); > + mutex_unlock(&vpr->lock); > + > + return 0; > +} > + > +static void tegra_vpr_device_release(struct reserved_mem *rmem, > + struct device *dev) > +{ > + struct tegra_vpr_device *node, *tmp; > + struct cma *cma = rmem->priv; > + struct tegra_vpr *vpr; > + > + vpr = tegra_vpr_lookup(cma); > + if (IS_ERR(vpr)) { > + dev_WARN(dev, "failed to find VPR for CMA '%s'\n", > + cma_get_name(cma)); > + return; > + } > + > + mutex_lock(&vpr->lock); > + > + list_for_each_entry_safe(node, tmp, &vpr->devices, node) { > + if (node->dev == dev) { > + list_del(&node->node); > + kfree(node); > + } > + } > + > + mutex_unlock(&vpr->lock); > +} > + > +static const struct reserved_mem_ops tegra_vpr_rmem_ops = { > + .node_init = tegra_vpr_node_init, > + .device_init = tegra_vpr_device_init, > + .device_release = tegra_vpr_device_release, > +}; > + > +RESERVEDMEM_OF_DECLARE(tegra_vpr, "nvidia,tegra-video-protection-region", > + &tegra_vpr_rmem_ops); > + > +void tegra_vpr_add(struct tegra_vpr *vpr) > +{ > + mutex_lock(&vpr_lock); > + list_add_tail(&vpr->list, &vpr_list); > + mutex_unlock(&vpr_lock); > +} > +EXPORT_SYMBOL(tegra_vpr_add); > diff --git a/drivers/dma-buf/heaps/tegra-vpr.c b/drivers/dma-buf/heaps/tegra-vpr.c > new file mode 100644 > index 000000000000..d8cff7e07ea0 > --- /dev/null > +++ b/drivers/dma-buf/heaps/tegra-vpr.c > @@ -0,0 +1,1210 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * DMA-BUF restricted heap exporter for NVIDIA Video-Protection-Region (VPR) > + * > + * Copyright (C) 2024-2026 NVIDIA Corporation > + */ > + > +#define pr_fmt(fmt) "tegra-vpr: " fmt > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "tegra-vpr.h" > + > +MODULE_IMPORT_NS("DMA_BUF_HEAP"); > +MODULE_IMPORT_NS("DMA_BUF"); > + > +#define CREATE_TRACE_POINTS > +#include > + > +#define TEGRA_VPR_MAX_CHUNKS 64 > + > +struct tegra_vpr_buffer { > + struct list_head attachments; > + struct tegra_vpr *vpr; > + struct list_head list; > + > + /** > + * @lock: Protects concurrent access to the list of attachments. > + */ > + struct mutex lock; > + > + struct page **pages; > + pgoff_t num_pages; > + phys_addr_t start; > + phys_addr_t limit; > + size_t size; > + int pageno; > + int order; > + > + DECLARE_BITMAP(chunks, TEGRA_VPR_MAX_CHUNKS); > +}; > + > +struct tegra_vpr_attachment { > + struct device *dev; > + struct sg_table sgt; > + struct list_head list; > +}; > + > +#define ARM_SMCCC_TE_FUNC_PROGRAM_VPR 0x3 > + > +#define ARM_SMCCC_VENDOR_SIP_TE_PROGRAM_VPR_FUNC_ID \ > + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ > + ARM_SMCCC_SMC_32, \ > + ARM_SMCCC_OWNER_SIP, \ > + ARM_SMCCC_TE_FUNC_PROGRAM_VPR) > + > +static int tegra_vpr_set(phys_addr_t base, phys_addr_t size) > +{ > + struct arm_smccc_res res; > + > + arm_smccc_smc(ARM_SMCCC_VENDOR_SIP_TE_PROGRAM_VPR_FUNC_ID, base, size, > + 0, 0, 0, 0, 0, &res); > + > + return res.a0; > +} > + > +static int tegra_vpr_get_extents(struct tegra_vpr *vpr, phys_addr_t *base, > + phys_addr_t *size) > +{ > + phys_addr_t start = ~0, limit = 0; > + unsigned int i; > + > + for (i = 0; i < vpr->num_chunks; i++) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + if (chunk->active) { > + if (chunk->start < start) > + start = chunk->start; > + > + if (chunk->limit > limit) > + limit = chunk->limit; > + } > + } > + > + if (limit > start) { > + *size = limit - start; > + *base = start; > + } else { > + *base = *size = 0; > + } > + > + return 0; > +} > + > +static int tegra_vpr_resize(struct tegra_vpr *vpr) > +{ > + struct tegra_vpr_device *node; > + phys_addr_t base, size; > + int err, status = 0; > + > + err = tegra_vpr_get_extents(vpr, &base, &size); > + if (err < 0) { > + pr_err("%s(): failed to get VPR extents: %d\n", __func__, err); > + return err; > + } > + > + list_for_each_entry(node, &vpr->devices, node) { > + err = pm_generic_freeze(node->dev); > + if (err < 0) { > + pr_err("failed to freeze %s: %d\n", > + dev_name(node->dev), err); > + status = err; > + goto thaw; > + } > + } > + > + trace_tegra_vpr_set(base, size); > + > + err = tegra_vpr_set(base, size); > + if (err < 0) { > + pr_err("failed to secure VPR: %d\n", err); > + status = err; > + } > + > +thaw: > + list_for_each_entry_continue_reverse(node, &vpr->devices, node) { > + err = pm_generic_thaw(node->dev); > + if (err < 0) { > + pr_err("failed to thaw %s: %d\n", > + dev_name(node->dev), err); > + continue; > + } > + } > + > + return status; > +} > + > +static int tegra_vpr_chunk_init(struct tegra_vpr *vpr, > + struct tegra_vpr_chunk *chunk, > + phys_addr_t start, size_t size, > + unsigned int order, const char *name) > +{ > + chunk->start = start; > + chunk->limit = start + size; > + chunk->size = size; > + chunk->vpr = vpr; > + > + chunk->offset = (start - vpr->base) >> PAGE_SHIFT; > + chunk->num_pages = size >> PAGE_SHIFT; > + chunk->num_buffers = 0; > + > + /* CMA area is not reserved yet */ > + chunk->start_page = NULL; > + chunk->virt = 0; > + > + return 0; > +} > + > +static void tegra_vpr_chunk_free(struct tegra_vpr_chunk *chunk) > +{ > +} > + > +static inline bool tegra_vpr_chunk_is_last(const struct tegra_vpr_chunk *chunk) > +{ > + phys_addr_t limit = chunk->vpr->base + chunk->vpr->size; > + > + return chunk->limit == limit; > +} > + > +static inline bool tegra_vpr_chunk_is_leaf(const struct tegra_vpr_chunk *chunk) > +{ > + const struct tegra_vpr_chunk *next = chunk + 1; > + > + if (tegra_vpr_chunk_is_last(chunk)) > + return true; > + > + return !next->active; > +} > + > +static int tegra_vpr_chunk_alloc(struct tegra_vpr_chunk *chunk) > +{ > + chunk->start_page = cma_alloc_at(chunk->vpr->cma, chunk->offset, > + chunk->num_pages, false); > + if (!chunk->start_page) > + return -ENOMEM; > + > + chunk->virt = (unsigned long)page_to_virt(chunk->start_page); > + > + return 0; > +} > + > +static int tegra_vpr_chunk_activate(struct tegra_vpr_chunk *chunk) > +{ > + int err; > + > + trace_tegra_vpr_chunk_activate(chunk->start, chunk->limit); > + > + err = set_direct_map_invalid_noflush(chunk->start_page, > + chunk->num_pages); > + if (err) > + return err; > + > + flush_tlb_kernel_range(chunk->virt, chunk->virt + chunk->size); > + chunk->invalid = false; > + chunk->active = true; > + > + return 0; > +} > + > +static int tegra_vpr_chunk_deactivate(struct tegra_vpr_chunk *chunk) > +{ > + int err; > + > + if (!chunk->active) > + return 0; > + > + /* do not deactivate if there are buffers left in this chunk */ > + if (WARN_ON(chunk->num_buffers > 0)) > + return -EBUSY; > + > + trace_tegra_vpr_chunk_deactivate(chunk->start, chunk->limit); > + > + err = set_direct_map_default_noflush(chunk->start_page, > + chunk->num_pages); > + if (err) > + return err; > + > + flush_tlb_kernel_range(chunk->virt, chunk->virt + chunk->size); > + chunk->invalid = false; > + chunk->active = false; > + > + return 0; > +} > + > +static void tegra_vpr_chunk_release(struct tegra_vpr_chunk *chunk) > +{ > + if (!WARN_ON(chunk->active || chunk->invalid)) { > + cma_release(chunk->vpr->cma, chunk->start_page, > + chunk->num_pages); > + chunk->start_page = NULL; > + chunk->virt = 0; > + } > +} > + > +static bool tegra_vpr_chunk_overlaps(struct tegra_vpr_chunk *chunk, > + unsigned int start, unsigned int limit) > +{ > + unsigned int first = chunk->offset; > + unsigned int last = chunk->offset + chunk->num_pages - 1; > + > + if (last < start || first >= limit) > + return false; > + > + return true; > +} > + > +static int tegra_vpr_activate_chunks(struct tegra_vpr *vpr, > + struct tegra_vpr_buffer *buffer) > +{ > + DECLARE_BITMAP(dirty, vpr->num_chunks); > + unsigned int i, bottom, top; > + int err = 0, ret; > + > + bitmap_zero(dirty, vpr->num_chunks); > + > + /* activate any inactive chunks that overlap this buffer */ > + for_each_set_bit(i, buffer->chunks, vpr->num_chunks) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + if (chunk->active) > + continue; > + > + err = tegra_vpr_chunk_alloc(chunk); > + if (err < 0) > + goto deactivate; > + > + err = tegra_vpr_chunk_activate(chunk); > + if (err < 0) { > + tegra_vpr_chunk_release(chunk); > + goto deactivate; > + } > + > + set_bit(i, vpr->active); > + set_bit(i, dirty); > + } > + > + /* > + * Activating chunks above may have created holes, but since the VPR > + * can only ever be a single contiguous region, make sure to activate > + * any missing chunks. > + */ > + for_each_clear_bitrange(bottom, top, vpr->active, vpr->num_chunks) { > + /* inactive chunks at the bottom or the top are harmless */ > + if (bottom == 0 || top == vpr->num_chunks) > + continue; > + > + for (i = bottom; i < top; i++) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + err = tegra_vpr_chunk_alloc(chunk); > + if (err < 0) > + goto deactivate; > + > + err = tegra_vpr_chunk_activate(chunk); > + if (err < 0) { > + tegra_vpr_chunk_release(chunk); > + goto deactivate; > + } > + > + set_bit(i, vpr->active); > + set_bit(i, dirty); > + } > + } > + > + /* if any chunks have been activated, VPR needs to be resized */ > + if (!bitmap_empty(dirty, vpr->num_chunks)) { > + err = tegra_vpr_resize(vpr); > + if (err < 0) { > + pr_err("failed to grow VPR: %d\n", err); > + goto deactivate; > + } > + } > + > + /* increment buffer count for each chunk */ > + for_each_set_bit(i, buffer->chunks, vpr->num_chunks) > + vpr->chunks[i].num_buffers++; > + > + return 0; > + > +deactivate: > + /* deactivate any of the previously inactive chunks on failure */ > + for_each_set_bit(i, dirty, vpr->num_chunks) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + ret = tegra_vpr_chunk_deactivate(chunk); > + if (WARN_ON(ret < 0)) { > + pr_err("failed to deactivate chunk #%u: %d\n", i, ret); > + } else { > + tegra_vpr_chunk_release(chunk); > + clear_bit(i, vpr->active); > + } > + } > + > + return err; > +} > + > +/* > + * Retrieve the range of pages within the activate region of the VPR. > + */ > +static bool tegra_vpr_get_active_range(struct tegra_vpr *vpr, > + unsigned int *first, > + unsigned int *last) > +{ > + unsigned long i, j; > + > + i = find_first_bit(vpr->active, vpr->num_chunks); > + if (i >= vpr->num_chunks) > + return false; > + > + j = find_last_bit(vpr->active, vpr->num_chunks); > + if (j >= vpr->num_chunks) > + return false; > + > + *first = vpr->chunks[i].offset; > + *last = vpr->chunks[j].offset + vpr->chunks[j].num_pages; > + > + return true; > +} > + > +/* > + * Try to find and allocate a free region within a specific page range. > + * Returns the page number if successful, -ENOSPC otherwise. > + * > + * This function mimics bitmap_find_free_region() but restricts the search > + * to a specific range to enable allocation within individual chunks. > + */ > +static int tegra_vpr_find_free_region_in_range(struct tegra_vpr *vpr, > + unsigned int start_page, > + unsigned int end_page, > + unsigned int num_pages, > + unsigned int align) > +{ > + unsigned int pos, next = ALIGN(start_page, align); > + > + /* Scan through aligned positions, trying to allocate at each one */ > + for (pos = next; pos + num_pages <= end_page; pos = next) { > + next = find_next_bit(vpr->bitmap, pos + num_pages, pos); > + > + if (next >= pos + num_pages) { > + bitmap_set(vpr->bitmap, pos, num_pages); > + return pos; > + } > + > + next = find_next_zero_bit(vpr->bitmap, vpr->num_pages, next); > + next = ALIGN(next, align); > + } > + > + return -ENOSPC; > +} > + > +static int tegra_vpr_find_free_region(struct tegra_vpr *vpr, > + unsigned int num_pages, > + unsigned long align) > +{ > + return tegra_vpr_find_free_region_in_range(vpr, 0, vpr->num_pages - 1, > + num_pages, align); > +} > + > +static int tegra_vpr_find_free_region_clustered(struct tegra_vpr *vpr, > + unsigned int num_pages, > + unsigned int align) > +{ > + unsigned int target, first, last; > + int pageno; > + > + /* > + * If there are no allocations, abort the clustered allocation scheme > + * and use the generic allocation scheme instead. > + */ > + if (vpr->first > vpr->last) > + return -ENOSPC; > + > + /* > + * First, try to allocate within the currently allocated region. This > + * keeps allocations tightly packed and minimizes the VPR size needed. > + */ > + pageno = tegra_vpr_find_free_region_in_range(vpr, vpr->first, > + vpr->last + 1, num_pages, > + align); > + if (pageno >= 0) > + return pageno; > + > + /* > + * If not enough free space exists within the currently allocated > + * region, check to see if the allocation fits anywhere within the > + * active region, avoiding the need to resize the VPR. > + */ > + if (tegra_vpr_get_active_range(vpr, &first, &last)) { > + pageno = tegra_vpr_find_free_region_in_range(vpr, first, last, > + num_pages, align); > + if (pageno >= 0) > + return pageno; > + } > + > + /* > + * If not enough free space exists within the currently active region, > + * try to allocate adjacent to it to grow it contiguously and ensure > + * optimal packing. > + */ > + > + /* > + * Calculate where the allocation should start to end right at the > + * first allocated page, with proper alignment. > + */ > + if (vpr->first >= num_pages) { > + target = ALIGN_DOWN(vpr->first - num_pages, align); > + > + if (!bitmap_allocate(vpr->bitmap, target, num_pages)) > + return target; > + } > + > + /* Try after the last allocation */ > + target = ALIGN(vpr->last + 1, align); > + > + if (target + num_pages <= vpr->num_pages && > + !bitmap_allocate(vpr->bitmap, target, num_pages)) > + return target; > + > + /* > + * Couldn't allocate at the ideal adjacent position, search for any > + * available space before the first allocated page. > + */ > + pageno = tegra_vpr_find_free_region_in_range(vpr, 0, vpr->first, > + num_pages, align); > + if (pageno >= 0) > + return pageno; > + > + /* > + * Couldn't allocate at the ideal adjacent position, search > + * for any available space after the last allocated page. > + */ > + pageno = tegra_vpr_find_free_region_in_range(vpr, vpr->last + 1, > + vpr->num_pages, num_pages, > + align); > + if (pageno >= 0) > + return pageno; > + > + return -ENOSPC; > +} > + > +/* > + * Find a free region, preferring locations near existing allocations to > + * minimize VPR fragmentation. The allocation strategy is to first allocate > + * within or adjacent to the existing region to keep allocations clustered. > + * Otherwise fall back to a generic allocation using the first available > + * space. > + * > + * This approach focuses on page-level allocation first, then the chunk > + * system determines which chunks need to be activated based on where the > + * pages ended up. > + */ > +static int tegra_vpr_allocate_region(struct tegra_vpr *vpr, > + unsigned int num_pages, > + unsigned int align) > +{ > + int pageno; > + > + /* > + * For non-resizable VPR (no chunks), use simple first-fit allocation. > + * Clustering optimization is only beneficial for resizable VPR where > + * keeping allocations together minimizes the active VPR size. > + */ > + if (!vpr->resizable) > + return tegra_vpr_find_free_region(vpr, num_pages, align); > + > + /* > + * Check if there are any existing allocations in the bitmap. If so, > + * try to allocate near them to minimize fragmentation. > + */ > + pageno = tegra_vpr_find_free_region_clustered(vpr, num_pages, align); > + if (pageno >= 0) > + return pageno; > + > + /* > + * If there are no existing allocations, or no space adjacent to them, > + * fall back to the first available space anywhere in the VPR. > + */ > + pageno = tegra_vpr_find_free_region(vpr, num_pages, align); > + if (pageno >= 0) > + return pageno; > + > + return -ENOSPC; > +} > + > +static struct tegra_vpr_buffer * > +tegra_vpr_buffer_allocate(struct tegra_vpr *vpr, size_t size) > +{ > + unsigned int num_pages = size >> PAGE_SHIFT; > + unsigned int order = get_order(size); > + struct tegra_vpr_buffer *buffer; > + unsigned long first, last; > + int pageno, err; > + > + /* > + * Quick sanity check that we're not trying to allocate a buffer that > + * has no chance of fitting into the VPR. > + */ > + if (size > vpr->size) > + return ERR_PTR(-EINVAL); > + > + /* > + * "order" defines the alignment and size, so this may result in > + * fragmented memory depending on the allocation patterns. However, > + * since this is used primarily for video frames, it is expected that > + * a number of buffers of the same size will be allocated, so > + * fragmentation should be negligible. > + */ > + pageno = tegra_vpr_allocate_region(vpr, num_pages, 1); > + if (pageno < 0) > + return ERR_PTR(pageno); > + > + first = find_first_bit(vpr->bitmap, vpr->num_pages); > + last = find_last_bit(vpr->bitmap, vpr->num_pages); > + > + buffer = kzalloc_obj(*buffer, GFP_KERNEL); > + if (!buffer) { > + err = -ENOMEM; > + goto release; > + } > + > + INIT_LIST_HEAD(&buffer->attachments); > + INIT_LIST_HEAD(&buffer->list); > + mutex_init(&buffer->lock); > + buffer->start = vpr->base + (pageno << PAGE_SHIFT); > + buffer->limit = buffer->start + size; > + buffer->size = size; > + buffer->num_pages = num_pages; > + buffer->pageno = pageno; > + buffer->order = order; > + > + /* track which chunks this buffer overlaps */ > + if (vpr->resizable) { > + unsigned int limit = buffer->pageno + buffer->num_pages; > + pgoff_t i; > + > + /* > + * Memory is backed by struct page, so track which ones we > + * use. > + */ > + buffer->pages = kvmalloc_array(buffer->num_pages, > + sizeof(*buffer->pages), > + GFP_KERNEL); > + if (!buffer->pages) { > + err = -ENOMEM; > + goto free; > + } > + > + for (i = 0; i < buffer->num_pages; i++) > + buffer->pages[i] = &vpr->start_page[pageno + i]; > + > + for (i = 0; i < vpr->num_chunks; i++) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + if (tegra_vpr_chunk_overlaps(chunk, pageno, limit)) > + set_bit(i, buffer->chunks); > + } > + > + /* activate chunks if necessary */ > + err = tegra_vpr_activate_chunks(vpr, buffer); > + if (err < 0) { > + kfree(buffer->pages); > + goto free; > + } > + > + /* track first and last allocated pages */ > + if (buffer->pageno < vpr->first) > + vpr->first = buffer->pageno; > + > + if (limit - 1 > vpr->last) > + vpr->last = limit - 1; > + } > + > + return buffer; > + > +free: > + kfree(buffer); > +release: > + bitmap_clear(vpr->bitmap, pageno, num_pages); > + return ERR_PTR(err); > +} > + > +static void tegra_vpr_buffer_release(struct tegra_vpr_buffer *buffer) > +{ > + struct tegra_vpr *vpr = buffer->vpr; > + struct tegra_vpr_buffer *entry; > + unsigned int i; > + > + /* > + * Decrement buffer count for each overlapping chunk. Note that chunks > + * are not deactivated here yet, that's done in tegra_vpr_recycle() > + * instead. > + */ > + for_each_set_bit(i, buffer->chunks, vpr->num_chunks) { > + if (!WARN_ON(vpr->chunks[i].num_buffers == 0)) > + vpr->chunks[i].num_buffers--; > + } > + > + /* track first and last allocated pages */ > + if (list_is_first(&buffer->list, &vpr->buffers) && > + list_is_last(&buffer->list, &vpr->buffers)) { > + /* if there are no remaining buffers after this, reset */ > + vpr->first = ~0U; > + vpr->last = 0U; > + } else if (list_is_first(&buffer->list, &vpr->buffers)) { > + entry = list_next_entry(buffer, list); > + vpr->first = entry->pageno; > + } else if (list_is_last(&buffer->list, &vpr->buffers)) { > + entry = list_prev_entry(buffer, list); > + vpr->last = entry->pageno + entry->num_pages - 1; > + } > + > + bitmap_clear(vpr->bitmap, buffer->pageno, buffer->num_pages); > + list_del(&buffer->list); > + kfree(buffer->pages); > + kfree(buffer); > +} > + > +static int tegra_vpr_attach(struct dma_buf *buf, > + struct dma_buf_attachment *attachment) > +{ > + struct tegra_vpr_buffer *buffer = buf->priv; > + struct tegra_vpr_attachment *attach; > + int err; > + > + attach = kzalloc_obj(*attach, GFP_KERNEL); > + if (!attach) > + return -ENOMEM; > + > + /* > + * For resizable VPR, the memory is backed by struct page, so we can > + * use the convenient helper to create the SG table. > + */ > + if (buffer->pages) { > + err = sg_alloc_table_from_pages(&attach->sgt, buffer->pages, > + buffer->num_pages, 0, > + buffer->size, GFP_KERNEL); > + if (err < 0) > + goto free; > + } else { > + if (sg_alloc_table(&attach->sgt, 1, GFP_KERNEL)) { > + err = -ENOMEM; > + goto free; > + } > + > + sg_set_page(attach->sgt.sgl, NULL, buffer->size, 0); > + sg_dma_address(attach->sgt.sgl) = buffer->start; > + sg_dma_len(attach->sgt.sgl) = buffer->size; > + } > + > + attach->dev = attachment->dev; > + INIT_LIST_HEAD(&attach->list); > + attachment->priv = attach; > + > + mutex_lock(&buffer->lock); > + list_add(&attach->list, &buffer->attachments); > + mutex_unlock(&buffer->lock); > + > + return 0; > + > +free: > + kfree(attach); > + return err; > +} > + > +static void tegra_vpr_detach(struct dma_buf *buf, > + struct dma_buf_attachment *attachment) > +{ > + struct tegra_vpr_buffer *buffer = buf->priv; > + struct tegra_vpr_attachment *attach = attachment->priv; > + > + mutex_lock(&buffer->lock); > + list_del(&attach->list); > + mutex_unlock(&buffer->lock); > + > + sg_free_table(&attach->sgt); > + kfree(attach); > +} > + > +static struct sg_table * > +tegra_vpr_map_dma_buf(struct dma_buf_attachment *attachment, > + enum dma_data_direction direction) > +{ > + struct tegra_vpr_attachment *attach = attachment->priv; > + struct sg_table *sgt = &attach->sgt; > + int err; > + > + err = dma_map_sgtable(attachment->dev, sgt, direction, > + DMA_ATTR_SKIP_CPU_SYNC); > + if (err < 0) > + return ERR_PTR(err); > + > + return sgt; > +} > + > +static void tegra_vpr_unmap_dma_buf(struct dma_buf_attachment *attachment, > + struct sg_table *sgt, > + enum dma_data_direction direction) > +{ > + dma_unmap_sgtable(attachment->dev, sgt, direction, > + DMA_ATTR_SKIP_CPU_SYNC); > +} > + > +static void tegra_vpr_recycle(struct tegra_vpr *vpr) > +{ > + DECLARE_BITMAP(dirty, vpr->num_chunks); > + unsigned int i; > + int err; > + > + if (!vpr->resizable) > + return; > + > + bitmap_zero(dirty, vpr->num_chunks); > + > + /* > + * Deactivate any unused chunks from the bottom... > + */ > + for (i = 0; i < vpr->num_chunks; i++) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + if (!chunk->active) > + continue; > + > + if (chunk->num_buffers > 0) > + break; > + > + err = tegra_vpr_chunk_deactivate(chunk); > + if (err < 0) { > + pr_err("failed to deactivate chunk #%u: %d\n", i, err); > + goto activate; > + } else { > + clear_bit(i, vpr->active); > + set_bit(i, dirty); > + } > + } > + > + /* > + * ... and the top. > + */ > + for (i = 0; i < vpr->num_chunks; i++) { > + unsigned int index = vpr->num_chunks - i - 1; > + struct tegra_vpr_chunk *chunk = &vpr->chunks[index]; > + > + if (!chunk->active) > + continue; > + > + if (chunk->num_buffers > 0) > + break; > + > + err = tegra_vpr_chunk_deactivate(chunk); > + if (err < 0) { > + pr_err("failed to deactivate chunk #%u: %d\n", index, > + err); > + goto activate; > + } else { > + clear_bit(index, vpr->active); > + set_bit(index, dirty); > + } > + } > + > + if (!bitmap_empty(dirty, vpr->num_chunks)) { > + err = tegra_vpr_resize(vpr); > + if (err < 0) { > + pr_err("failed to shrink VPR: %d\n", err); > + goto activate; > + } > + } > + > + /* release the CMA memory associated with deactivated chunks */ > + for_each_set_bit(i, dirty, vpr->num_chunks) > + tegra_vpr_chunk_release(&vpr->chunks[i]); > + > + return; > + > +activate: > + for_each_set_bit(i, dirty, vpr->num_chunks) { > + err = tegra_vpr_chunk_activate(&vpr->chunks[i]); > + if (WARN_ON(err < 0)) > + pr_err("failed to activate chunk #%u: %d\n", i, err); > + > + /* > + * This may not be fully activated at this point, but we need > + * to keep track of it anyway to make sure the CMA region can > + * eventually be released. The WARN_ON above tells us when it > + * happens: here be dragons. > + */ > + set_bit(i, vpr->active); > + } > +} > + > +static void tegra_vpr_release(struct dma_buf *buf) > +{ > + struct tegra_vpr_buffer *buffer = buf->priv; > + struct tegra_vpr *vpr = buffer->vpr; > + > + mutex_lock(&vpr->lock); > + > + tegra_vpr_buffer_release(buffer); > + tegra_vpr_recycle(vpr); > + > + mutex_unlock(&vpr->lock); > +} > + > +/* > + * Prohibit userspace mapping because the CPU cannot access this memory > + * anyway. > + */ > +static int tegra_vpr_begin_cpu_access(struct dma_buf *buf, > + enum dma_data_direction direction) > +{ > + return -EPERM; > +} > + > +static int tegra_vpr_end_cpu_access(struct dma_buf *buf, > + enum dma_data_direction direction) > +{ > + return -EPERM; > +} > + > +static int tegra_vpr_mmap(struct dma_buf *buf, struct vm_area_struct *vma) > +{ > + return -EPERM; > +} > + > +static const struct dma_buf_ops tegra_vpr_buf_ops = { > + .attach = tegra_vpr_attach, > + .detach = tegra_vpr_detach, > + .map_dma_buf = tegra_vpr_map_dma_buf, > + .unmap_dma_buf = tegra_vpr_unmap_dma_buf, > + .release = tegra_vpr_release, > + .begin_cpu_access = tegra_vpr_begin_cpu_access, > + .end_cpu_access = tegra_vpr_end_cpu_access, > + .mmap = tegra_vpr_mmap, > +}; > + > +static struct dma_buf *tegra_vpr_allocate(struct dma_heap *heap, > + unsigned long len, u32 fd_flags, > + u64 heap_flags) > +{ > + struct tegra_vpr *vpr = dma_heap_get_drvdata(heap); > + struct tegra_vpr_buffer *buffer, *entry; > + size_t size = ALIGN(len, vpr->align); > + DEFINE_DMA_BUF_EXPORT_INFO(export); > + struct dma_buf *buf; > + > + mutex_lock(&vpr->lock); > + > + buffer = tegra_vpr_buffer_allocate(vpr, size); > + if (IS_ERR(buffer)) { > + mutex_unlock(&vpr->lock); > + return ERR_CAST(buffer); > + } > + > + /* insert in the correct order */ > + if (!list_empty(&vpr->buffers)) { > + list_for_each_entry(entry, &vpr->buffers, list) { > + if (buffer->pageno < entry->pageno) { > + list_add_tail(&buffer->list, &entry->list); > + break; > + } > + } > + } > + > + if (list_empty(&buffer->list)) > + list_add_tail(&buffer->list, &vpr->buffers); > + > + buffer->vpr = vpr; > + > + /* > + * If a valid buffer was allocated, wrap it in a dma_buf > + * and return it. > + */ > + export.exp_name = dma_heap_get_name(heap); > + export.ops = &tegra_vpr_buf_ops; > + export.size = buffer->size; > + export.flags = fd_flags; > + export.priv = buffer; > + > + buf = dma_buf_export(&export); > + if (IS_ERR(buf)) { > + tegra_vpr_buffer_release(buffer); > + tegra_vpr_recycle(vpr); > + } > + > + mutex_unlock(&vpr->lock); > + return buf; > +} > + > +static void tegra_vpr_debugfs_show_buffers(struct tegra_vpr *vpr, > + struct seq_file *s) > +{ > + struct tegra_vpr_buffer *buffer; > + char buf[16]; > + > + mutex_lock(&vpr->lock); > + > + list_for_each_entry(buffer, &vpr->buffers, list) { > + string_get_size(buffer->size, 1, STRING_UNITS_2, buf, > + sizeof(buf)); > + seq_printf(s, " %pap-%pap (%s)\n", &buffer->start, > + &buffer->limit, buf); > + } > + > + mutex_unlock(&vpr->lock); > +} > + > +static void tegra_vpr_debugfs_show_chunks(struct tegra_vpr *vpr, > + struct seq_file *s) > +{ > + struct tegra_vpr_buffer *buffer; > + unsigned int i; > + char buf[16]; > + > + for (i = 0; i < vpr->num_chunks; i++) { > + const struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + string_get_size(chunk->size, 1, STRING_UNITS_2, buf, > + sizeof(buf)); > + seq_printf(s, " %pap-%pap (%s) (%s, %u buffers)\n", > + &chunk->start, &chunk->limit, buf, > + chunk->active ? "active" : "inactive", > + chunk->num_buffers); > + } > + > + list_for_each_entry(buffer, &vpr->buffers, list) { > + string_get_size(buffer->size, 1, STRING_UNITS_2, buf, > + sizeof(buf)); > + seq_printf(s, "%pap-%pap (%s, chunks: %*pbl)\n", > + &buffer->start, &buffer->limit, buf, > + vpr->num_chunks, buffer->chunks); > + } > +} > + > +static int tegra_vpr_debugfs_show(struct seq_file *s, struct dma_heap *heap) > +{ > + struct tegra_vpr *vpr = dma_heap_get_drvdata(heap); > + phys_addr_t limit = vpr->base + vpr->size; > + char buf[16]; > + > + string_get_size(vpr->size, 1, STRING_UNITS_2, buf, sizeof(buf)); > + seq_printf(s, "%pap-%pap (%s)\n", &vpr->base, &limit, buf); > + > + if (!vpr->resizable) > + tegra_vpr_debugfs_show_buffers(vpr, s); > + else > + tegra_vpr_debugfs_show_chunks(vpr, s); > + > + return 0; > +} > + > +static const struct dma_heap_ops tegra_vpr_heap_ops = { > + .allocate = tegra_vpr_allocate, > + .show = tegra_vpr_debugfs_show, > +}; > + > +static int tegra_vpr_setup_chunks(struct tegra_vpr *vpr, const char *name) > +{ > + phys_addr_t start, limit; > + unsigned int order, i = 0; > + size_t max_size; > + int err; > + > + /* Memory is backed by struct page, so track the first one. */ > + vpr->start_page = phys_to_page(vpr->base); > + > + /* This seems a reasonable value, so hard-code it for now. */ > + vpr->num_chunks = 4; > + > + vpr->chunks = kzalloc_objs(*vpr->chunks, vpr->num_chunks); > + if (!vpr->chunks) > + return -ENOMEM; > + > + vpr->active = bitmap_zalloc(vpr->num_chunks, GFP_KERNEL); > + if (!vpr->active) { > + err = -ENOMEM; > + goto free; > + } > + > + max_size = PAGE_SIZE << (get_order(vpr->size) - ilog2(vpr->num_chunks)); > + order = get_order(vpr->align); > + > + /* > + * Allocate CMA areas for VPR. All areas will be roughtly the same > + * size, with the last area taking up the rest. > + */ > + start = vpr->base; > + limit = vpr->base + vpr->size; > + > + pr_debug("VPR: %pap-%pap (%lu pages, %u chunks, %lu MiB)\n", &start, > + &limit, vpr->num_pages, vpr->num_chunks, > + (unsigned long)vpr->size / 1024 / 1024); > + > + for (i = 0; i < vpr->num_chunks; i++) { > + size_t size = limit - start; > + phys_addr_t end; > + > + size = min_t(size_t, size, max_size); > + end = start + size - 1; > + > + err = tegra_vpr_chunk_init(vpr, &vpr->chunks[i], start, size, > + order, name); > + if (err < 0) { > + pr_err("failed to create VPR chunk: %d\n", err); > + goto free; > + } > + > + pr_debug(" %2u: %pap-%pap (%lu MiB)\n", i, &start, &end, > + size / 1024 / 1024); > + start += size; > + } > + > + vpr->first = ~0U; > + vpr->last = 0U; > + > + return 0; > + > +free: > + while (i--) > + tegra_vpr_chunk_free(&vpr->chunks[i]); > + > + kfree(vpr->active); > + kfree(vpr->chunks); > + return err; > +} > + > +static void tegra_vpr_free_chunks(struct tegra_vpr *vpr) > +{ > + unsigned int i; > + > + for (i = 0; i < vpr->num_chunks; i++) > + tegra_vpr_chunk_free(&vpr->chunks[i]); > + > + kfree(vpr->chunks); > +} > + > +static int tegra_vpr_setup_static(struct tegra_vpr *vpr) > +{ > + phys_addr_t start, limit; > + > + start = vpr->base; > + limit = vpr->base + vpr->size; > + > + pr_debug("VPR: %pap-%pap (%lu pages, %lu MiB)\n", &start, &limit, > + vpr->num_pages, (unsigned long)vpr->size / 1024 / 1024); > + > + return 0; > +} > + > +static int tegra_vpr_add_heap(struct reserved_mem *rmem, > + struct device_node *np) > +{ > + struct dma_heap_export_info info = {}; > + unsigned long first, last; > + struct dma_heap *heap; > + struct tegra_vpr *vpr; > + int err; > + > + vpr = kzalloc_obj(*vpr, GFP_KERNEL); > + if (!vpr) > + return -ENOMEM; > + > + INIT_LIST_HEAD(&vpr->list); > + INIT_LIST_HEAD(&vpr->buffers); > + INIT_LIST_HEAD(&vpr->devices); > + mutex_init(&vpr->lock); > + > + vpr->resizable = !of_property_read_bool(np, "no-map"); > + vpr->dev_node = of_node_get(np); > + vpr->align = PAGE_SIZE; > + vpr->base = rmem->base; > + vpr->size = rmem->size; > + vpr->num_pages = vpr->size >> PAGE_SHIFT; > + vpr->nid = of_node_to_nid(np); If this gets used, I can't find it. Rob