Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
CONFIG_KUNIT=y
CONFIG_DMABUF_KUNIT_TEST=y
+71
View File
@@ -0,0 +1,71 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "DMABUF options"
config SYNC_FILE
bool "Explicit Synchronization Framework"
default n
select DMA_SHARED_BUFFER
help
The Sync File Framework adds explicit synchronization via
userspace. It enables send/receive 'struct dma_fence' objects to/from
userspace via Sync File fds for synchronization between drivers via
userspace components. It has been ported from Android.
The first and main user for this is graphics in which a fence is
associated with a buffer. When a job is submitted to the GPU a fence
is attached to the buffer and is transferred via userspace, using Sync
Files fds, to the DRM driver for example. More details at
Documentation/driver-api/sync_file.rst.
config SW_SYNC
bool "Sync File Validation Framework"
default n
depends on SYNC_FILE
depends on DEBUG_FS
help
A sync object driver that uses a 32bit counter to coordinate
synchronization. Useful when there is no hardware primitive backing
the synchronization.
WARNING: improper use of this can result in deadlocking kernel
drivers from userspace. Intended for test and debug only.
config UDMABUF
bool "userspace dmabuf misc driver"
default n
depends on DMA_SHARED_BUFFER
depends on MEMFD_CREATE || COMPILE_TEST
depends on MMU
help
A driver to let userspace turn memfd regions into dma-bufs.
Qemu can use this to create host dmabufs for guest framebuffers.
config DMABUF_DEBUG
bool "DMA-BUF debug checks"
depends on DMA_SHARED_BUFFER
default y if DEBUG
help
This option enables additional checks for DMA-BUF importers and
exporters. Specifically it validates that importers do not peek at the
underlying struct page when they import a buffer.
config DMABUF_KUNIT_TEST
tristate "KUnit tests for DMA-BUF" if !KUNIT_ALL_TESTS
depends on KUNIT
select DMA_SHARED_BUFFER
default KUNIT_ALL_TESTS
help
Enable kunit tests for DMA-BUF
menuconfig DMABUF_HEAPS
bool "DMA-BUF Userland Memory Heaps"
select DMA_SHARED_BUFFER
help
Choose this option to enable the DMA-BUF userland memory heaps.
This options creates per heap chardevs in /dev/dma_heap/ which
allows userspace to allocate dma-bufs that can be shared
between drivers.
source "drivers/dma-buf/heaps/Kconfig"
endmenu
+16
View File
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \
dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o
obj-$(CONFIG_DMABUF_HEAPS) += dma-heap.o
obj-$(CONFIG_DMABUF_HEAPS) += heaps/
obj-$(CONFIG_SYNC_FILE) += sync_file.o
obj-$(CONFIG_SW_SYNC) += sw_sync.o sync_debug.o
obj-$(CONFIG_UDMABUF) += udmabuf.o
dmabuf_kunit-y := \
st-dma-fence.o \
st-dma-fence-chain.o \
st-dma-fence-unwrap.o \
st-dma-resv.o
obj-$(CONFIG_DMABUF_KUNIT_TEST) += dmabuf_kunit.o
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* DMA BUF Mapping Helpers
*
*/
#include <linux/dma-buf-mapping.h>
#include <linux/dma-resv.h>
static struct scatterlist *fill_sg_entry(struct scatterlist *sgl, size_t length,
dma_addr_t addr)
{
unsigned int len, nents;
int i;
nents = DIV_ROUND_UP(length, UINT_MAX);
for (i = 0; i < nents; i++) {
len = min_t(size_t, length, UINT_MAX);
length -= len;
/*
* DMABUF abuses scatterlist to create a scatterlist
* that does not have any CPU list, only the DMA list.
* Always set the page related values to NULL to ensure
* importers can't use it. The phys_addr based DMA API
* does not require the CPU list for mapping or unmapping.
*/
sg_set_page(sgl, NULL, 0, 0);
sg_dma_address(sgl) = addr + (dma_addr_t)i * UINT_MAX;
sg_dma_len(sgl) = len;
sgl = sg_next(sgl);
}
return sgl;
}
static unsigned int calc_sg_nents(struct dma_iova_state *state,
struct phys_vec *phys_vec, size_t nr_ranges,
size_t size)
{
unsigned int nents = 0;
size_t i;
if (!state || !dma_use_iova(state)) {
for (i = 0; i < nr_ranges; i++)
nents += DIV_ROUND_UP(phys_vec[i].len, UINT_MAX);
} else {
/*
* In IOVA case, there is only one SG entry which spans
* for whole IOVA address space, but we need to make sure
* that it fits sg->length, maybe we need more.
*/
nents = DIV_ROUND_UP(size, UINT_MAX);
}
return nents;
}
/**
* struct dma_buf_dma - holds DMA mapping information
* @sgt: Scatter-gather table
* @state: DMA IOVA state relevant in IOMMU-based DMA
* @size: Total size of DMA transfer
*/
struct dma_buf_dma {
struct sg_table sgt;
struct dma_iova_state *state;
size_t size;
};
/**
* dma_buf_phys_vec_to_sgt - Returns the scatterlist table of the attachment
* from arrays of physical vectors. This funciton is intended for MMIO memory
* only.
* @attach: [in] attachment whose scatterlist is to be returned
* @provider: [in] p2pdma provider
* @phys_vec: [in] array of physical vectors
* @nr_ranges: [in] number of entries in phys_vec array
* @size: [in] total size of phys_vec
* @dir: [in] direction of DMA transfer
*
* Returns sg_table containing the scatterlist to be returned; returns ERR_PTR
* on error. May return -EINTR if it is interrupted by a signal.
*
* On success, the DMA addresses and lengths in the returned scatterlist are
* PAGE_SIZE aligned.
*
* A mapping must be unmapped by using dma_buf_free_sgt().
*
* NOTE: This function is intended for exporters. If direct traffic routing is
* mandatory exporter should call routing pci_p2pdma_map_type() before calling
* this function.
*/
struct sg_table *dma_buf_phys_vec_to_sgt(struct dma_buf_attachment *attach,
struct p2pdma_provider *provider,
struct phys_vec *phys_vec,
size_t nr_ranges, size_t size,
enum dma_data_direction dir)
{
unsigned int nents, mapped_len = 0;
struct dma_buf_dma *dma;
struct scatterlist *sgl;
dma_addr_t addr;
size_t i;
int ret;
dma_resv_assert_held(attach->dmabuf->resv);
if (WARN_ON(!attach || !attach->dmabuf || !provider))
/* This function is supposed to work on MMIO memory only */
return ERR_PTR(-EINVAL);
dma = kzalloc_obj(*dma);
if (!dma)
return ERR_PTR(-ENOMEM);
switch (pci_p2pdma_map_type(provider, attach->dev)) {
case PCI_P2PDMA_MAP_BUS_ADDR:
/*
* There is no need in IOVA at all for this flow.
*/
break;
case PCI_P2PDMA_MAP_THRU_HOST_BRIDGE:
dma->state = kzalloc_obj(*dma->state);
if (!dma->state) {
ret = -ENOMEM;
goto err_free_dma;
}
dma_iova_try_alloc(attach->dev, dma->state, 0, size);
break;
default:
ret = -EINVAL;
goto err_free_dma;
}
nents = calc_sg_nents(dma->state, phys_vec, nr_ranges, size);
ret = sg_alloc_table(&dma->sgt, nents, GFP_KERNEL | __GFP_ZERO);
if (ret)
goto err_free_state;
sgl = dma->sgt.sgl;
for (i = 0; i < nr_ranges; i++) {
if (!dma->state) {
addr = pci_p2pdma_bus_addr_map(provider,
phys_vec[i].paddr);
} else if (dma_use_iova(dma->state)) {
ret = dma_iova_link(attach->dev, dma->state,
phys_vec[i].paddr, 0,
phys_vec[i].len, dir,
DMA_ATTR_MMIO);
if (ret)
goto err_unmap_dma;
mapped_len += phys_vec[i].len;
} else {
addr = dma_map_phys(attach->dev, phys_vec[i].paddr,
phys_vec[i].len, dir,
DMA_ATTR_MMIO);
ret = dma_mapping_error(attach->dev, addr);
if (ret)
goto err_unmap_dma;
}
if (!dma->state || !dma_use_iova(dma->state))
sgl = fill_sg_entry(sgl, phys_vec[i].len, addr);
}
if (dma->state && dma_use_iova(dma->state)) {
WARN_ON_ONCE(mapped_len != size);
ret = dma_iova_sync(attach->dev, dma->state, 0, mapped_len);
if (ret)
goto err_unmap_dma;
sgl = fill_sg_entry(sgl, mapped_len, dma->state->addr);
}
dma->size = size;
/*
* No CPU list included — set orig_nents = 0 so others can detect
* this via SG table (use nents only).
*/
dma->sgt.orig_nents = 0;
/*
* SGL must be NULL to indicate that SGL is the last one
* and we allocated correct number of entries in sg_alloc_table()
*/
WARN_ON_ONCE(sgl);
return &dma->sgt;
err_unmap_dma:
if (!i || !dma->state) {
; /* Do nothing */
} else if (dma_use_iova(dma->state)) {
dma_iova_destroy(attach->dev, dma->state, mapped_len, dir,
DMA_ATTR_MMIO);
} else {
for_each_sgtable_dma_sg(&dma->sgt, sgl, i)
dma_unmap_phys(attach->dev, sg_dma_address(sgl),
sg_dma_len(sgl), dir, DMA_ATTR_MMIO);
}
sg_free_table(&dma->sgt);
err_free_state:
kfree(dma->state);
err_free_dma:
kfree(dma);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_NS_GPL(dma_buf_phys_vec_to_sgt, "DMA_BUF");
/**
* dma_buf_free_sgt- unmaps the buffer
* @attach: [in] attachment to unmap buffer from
* @sgt: [in] scatterlist info of the buffer to unmap
* @dir: [in] direction of DMA transfer
*
* This unmaps a DMA mapping for @attached obtained
* by dma_buf_phys_vec_to_sgt().
*/
void dma_buf_free_sgt(struct dma_buf_attachment *attach, struct sg_table *sgt,
enum dma_data_direction dir)
{
struct dma_buf_dma *dma = container_of(sgt, struct dma_buf_dma, sgt);
int i;
dma_resv_assert_held(attach->dmabuf->resv);
if (!dma->state) {
; /* Do nothing */
} else if (dma_use_iova(dma->state)) {
dma_iova_destroy(attach->dev, dma->state, dma->size, dir,
DMA_ATTR_MMIO);
} else {
struct scatterlist *sgl;
for_each_sgtable_dma_sg(sgt, sgl, i)
dma_unmap_phys(attach->dev, sg_dma_address(sgl),
sg_dma_len(sgl), dir, DMA_ATTR_MMIO);
}
sg_free_table(sgt);
kfree(dma->state);
kfree(dma);
}
EXPORT_SYMBOL_NS_GPL(dma_buf_free_sgt, "DMA_BUF");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* dma-fence-array: aggregate fences to be waited together
*
* Copyright (C) 2016 Collabora Ltd
* Copyright (C) 2016 Advanced Micro Devices, Inc.
* Authors:
* Gustavo Padovan <gustavo@padovan.org>
* Christian König <christian.koenig@amd.com>
*/
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/dma-fence-array.h>
#define PENDING_ERROR 1
static const char *dma_fence_array_get_driver_name(struct dma_fence *fence)
{
return "dma_fence_array";
}
static const char *dma_fence_array_get_timeline_name(struct dma_fence *fence)
{
return "unbound";
}
static void dma_fence_array_set_pending_error(struct dma_fence_array *array,
int error)
{
/*
* Propagate the first error reported by any of our fences, but only
* before we ourselves are signaled.
*/
if (error)
cmpxchg(&array->base.error, PENDING_ERROR, error);
}
static void dma_fence_array_clear_pending_error(struct dma_fence_array *array)
{
/* Clear the error flag if not actually set. */
cmpxchg(&array->base.error, PENDING_ERROR, 0);
}
static void irq_dma_fence_array_work(struct irq_work *wrk)
{
struct dma_fence_array *array = container_of(wrk, typeof(*array), work);
dma_fence_array_clear_pending_error(array);
dma_fence_signal(&array->base);
dma_fence_put(&array->base);
}
static void dma_fence_array_cb_func(struct dma_fence *f,
struct dma_fence_cb *cb)
{
struct dma_fence_array_cb *array_cb =
container_of(cb, struct dma_fence_array_cb, cb);
struct dma_fence_array *array = array_cb->array;
dma_fence_array_set_pending_error(array, f->error);
if (atomic_dec_and_test(&array->num_pending))
irq_work_queue(&array->work);
else
dma_fence_put(&array->base);
}
static bool dma_fence_array_enable_signaling(struct dma_fence *fence)
{
struct dma_fence_array *array = to_dma_fence_array(fence);
struct dma_fence_array_cb *cb = array->callbacks;
unsigned i;
for (i = 0; i < array->num_fences; ++i) {
cb[i].array = array;
/*
* As we may report that the fence is signaled before all
* callbacks are complete, we need to take an additional
* reference count on the array so that we do not free it too
* early. The core fence handling will only hold the reference
* until we signal the array as complete (but that is now
* insufficient).
*/
dma_fence_get(&array->base);
if (dma_fence_add_callback(array->fences[i], &cb[i].cb,
dma_fence_array_cb_func)) {
int error = array->fences[i]->error;
dma_fence_array_set_pending_error(array, error);
dma_fence_put(&array->base);
if (atomic_dec_and_test(&array->num_pending)) {
dma_fence_array_clear_pending_error(array);
return false;
}
}
}
return true;
}
static bool dma_fence_array_signaled(struct dma_fence *fence)
{
struct dma_fence_array *array = to_dma_fence_array(fence);
int num_pending;
unsigned int i;
/*
* We need to read num_pending before checking the enable_signal bit
* to avoid racing with the enable_signaling() implementation, which
* might decrement the counter, and cause a partial check.
* atomic_read_acquire() pairs with atomic_dec_and_test() in
* dma_fence_array_enable_signaling()
*
* The !--num_pending check is here to account for the any_signaled case
* if we race with enable_signaling(), that means the !num_pending check
* in the is_signalling_enabled branch might be outdated (num_pending
* might have been decremented), but that's fine. The user will get the
* right value when testing again later.
*/
num_pending = atomic_read_acquire(&array->num_pending);
if (test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &array->base.flags)) {
if (num_pending <= 0)
goto signal;
return false;
}
for (i = 0; i < array->num_fences; ++i) {
if (dma_fence_is_signaled(array->fences[i]) && !--num_pending)
goto signal;
}
return false;
signal:
dma_fence_array_clear_pending_error(array);
return true;
}
static void dma_fence_array_release(struct dma_fence *fence)
{
struct dma_fence_array *array = to_dma_fence_array(fence);
unsigned i;
for (i = 0; i < array->num_fences; ++i)
dma_fence_put(array->fences[i]);
kfree(array->fences);
dma_fence_free(fence);
}
static void dma_fence_array_set_deadline(struct dma_fence *fence,
ktime_t deadline)
{
struct dma_fence_array *array = to_dma_fence_array(fence);
unsigned i;
for (i = 0; i < array->num_fences; ++i)
dma_fence_set_deadline(array->fences[i], deadline);
}
const struct dma_fence_ops dma_fence_array_ops = {
.get_driver_name = dma_fence_array_get_driver_name,
.get_timeline_name = dma_fence_array_get_timeline_name,
.enable_signaling = dma_fence_array_enable_signaling,
.signaled = dma_fence_array_signaled,
.release = dma_fence_array_release,
.set_deadline = dma_fence_array_set_deadline,
};
EXPORT_SYMBOL(dma_fence_array_ops);
/**
* dma_fence_array_alloc - Allocate a custom fence array
* @num_fences: [in] number of fences to add in the array
*
* Return dma fence array on success, NULL on failure
*/
struct dma_fence_array *dma_fence_array_alloc(int num_fences)
{
struct dma_fence_array *array;
return kzalloc_flex(*array, callbacks, num_fences);
}
EXPORT_SYMBOL(dma_fence_array_alloc);
/**
* dma_fence_array_init - Init a custom fence array
* @array: [in] dma fence array to arm
* @num_fences: [in] number of fences to add in the array
* @fences: [in] array containing the fences
* @context: [in] fence context to use
* @seqno: [in] sequence number to use
*
* Implementation of @dma_fence_array_create without allocation. Useful to init
* a preallocated dma fence array in the path of reclaim or dma fence signaling.
*/
void dma_fence_array_init(struct dma_fence_array *array,
int num_fences, struct dma_fence **fences,
u64 context, unsigned seqno)
{
static struct lock_class_key dma_fence_array_lock_key;
WARN_ON(!num_fences || !fences);
array->num_fences = num_fences;
dma_fence_init(&array->base, &dma_fence_array_ops, NULL, context,
seqno);
init_irq_work(&array->work, irq_dma_fence_array_work);
/*
* dma_fence_array_enable_signaling() is invoked while holding
* array->base.inline_lock and may call dma_fence_add_callback()
* on the underlying fences, which takes their inline_lock.
*
* Since both locks share the same lockdep class, this legitimate
* nesting confuses lockdep and triggers a recursive locking
* warning. Assign a separate lockdep class to the array lock
* to model this hierarchy correctly.
*/
lockdep_set_class(&array->base.inline_lock, &dma_fence_array_lock_key);
atomic_set(&array->num_pending, num_fences);
array->fences = fences;
array->base.error = PENDING_ERROR;
/*
* dma_fence_array objects should never contain any other fence
* containers or otherwise we run into recursion and potential kernel
* stack overflow on operations on the dma_fence_array.
*
* The correct way of handling this is to flatten out the array by the
* caller instead.
*
* Enforce this here by checking that we don't create a dma_fence_array
* with any container inside.
*/
while (num_fences--)
WARN_ON(dma_fence_is_container(fences[num_fences]));
}
EXPORT_SYMBOL(dma_fence_array_init);
/**
* dma_fence_array_create - Create a custom fence array
* @num_fences: [in] number of fences to add in the array
* @fences: [in] array containing the fences
* @context: [in] fence context to use
* @seqno: [in] sequence number to use
*
* Allocate a dma_fence_array object and initialize the base fence with
* dma_fence_init().
* In case of error it returns NULL.
*
* The caller should allocate the fences array with num_fences size
* and fill it with the fences it wants to add to the object. Ownership of this
* array is taken and dma_fence_put() is used on each fence on release.
*
* If @signal_on_any is true the fence array signals if any fence in the array
* signals, otherwise it signals when all fences in the array signal.
*/
struct dma_fence_array *dma_fence_array_create(int num_fences,
struct dma_fence **fences,
u64 context, unsigned seqno)
{
struct dma_fence_array *array;
array = dma_fence_array_alloc(num_fences);
if (!array)
return NULL;
dma_fence_array_init(array, num_fences, fences, context, seqno);
return array;
}
EXPORT_SYMBOL(dma_fence_array_create);
/**
* dma_fence_match_context - Check if all fences are from the given context
* @fence: [in] fence or fence array
* @context: [in] fence context to check all fences against
*
* Checks the provided fence or, for a fence array, all fences in the array
* against the given context. Returns false if any fence is from a different
* context.
*/
bool dma_fence_match_context(struct dma_fence *fence, u64 context)
{
struct dma_fence_array *array = to_dma_fence_array(fence);
unsigned i;
if (!dma_fence_is_array(fence))
return fence->context == context;
for (i = 0; i < array->num_fences; i++) {
if (array->fences[i]->context != context)
return false;
}
return true;
}
EXPORT_SYMBOL(dma_fence_match_context);
struct dma_fence *dma_fence_array_first(struct dma_fence *head)
{
struct dma_fence_array *array;
if (!head)
return NULL;
array = to_dma_fence_array(head);
if (!array)
return head;
if (!array->num_fences)
return NULL;
return array->fences[0];
}
EXPORT_SYMBOL(dma_fence_array_first);
struct dma_fence *dma_fence_array_next(struct dma_fence *head,
unsigned int index)
{
struct dma_fence_array *array = to_dma_fence_array(head);
if (!array || index >= array->num_fences)
return NULL;
return array->fences[index];
}
EXPORT_SYMBOL(dma_fence_array_next);
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fence-chain: chain fences together in a timeline
*
* Copyright (C) 2018 Advanced Micro Devices, Inc.
* Authors:
* Christian König <christian.koenig@amd.com>
*/
#include <linux/dma-fence-chain.h>
static bool dma_fence_chain_enable_signaling(struct dma_fence *fence);
/**
* dma_fence_chain_get_prev - use RCU to get a reference to the previous fence
* @chain: chain node to get the previous node from
*
* Use dma_fence_get_rcu_safe to get a reference to the previous fence of the
* chain node.
*/
static struct dma_fence *dma_fence_chain_get_prev(struct dma_fence_chain *chain)
{
struct dma_fence *prev;
rcu_read_lock();
prev = dma_fence_get_rcu_safe(&chain->prev);
rcu_read_unlock();
return prev;
}
/**
* dma_fence_chain_walk - chain walking function
* @fence: current chain node
*
* Walk the chain to the next node. Returns the next fence or NULL if we are at
* the end of the chain. Garbage collects chain nodes which are already
* signaled.
*/
struct dma_fence *dma_fence_chain_walk(struct dma_fence *fence)
{
struct dma_fence_chain *chain, *prev_chain;
struct dma_fence *prev, *replacement, *tmp;
chain = to_dma_fence_chain(fence);
if (!chain) {
dma_fence_put(fence);
return NULL;
}
while ((prev = dma_fence_chain_get_prev(chain))) {
prev_chain = to_dma_fence_chain(prev);
if (prev_chain) {
if (!dma_fence_is_signaled(prev_chain->fence))
break;
replacement = dma_fence_chain_get_prev(prev_chain);
} else {
if (!dma_fence_is_signaled(prev))
break;
replacement = NULL;
}
tmp = unrcu_pointer(cmpxchg(&chain->prev, RCU_INITIALIZER(prev),
RCU_INITIALIZER(replacement)));
if (tmp == prev)
dma_fence_put(tmp);
else
dma_fence_put(replacement);
dma_fence_put(prev);
}
dma_fence_put(fence);
return prev;
}
EXPORT_SYMBOL(dma_fence_chain_walk);
/**
* dma_fence_chain_find_seqno - find fence chain node by seqno
* @pfence: pointer to the chain node where to start
* @seqno: the sequence number to search for
*
* Advance the fence pointer to the chain node which will signal this sequence
* number. If no sequence number is provided then this is a no-op.
*
* Returns EINVAL if the fence is not a chain node or the sequence number has
* not yet advanced far enough.
*/
int dma_fence_chain_find_seqno(struct dma_fence **pfence, uint64_t seqno)
{
struct dma_fence_chain *chain;
if (!seqno)
return 0;
chain = to_dma_fence_chain(*pfence);
if (!chain || chain->base.seqno < seqno)
return -EINVAL;
dma_fence_chain_for_each(*pfence, &chain->base) {
if ((*pfence)->context != chain->base.context ||
to_dma_fence_chain(*pfence)->prev_seqno < seqno)
break;
}
dma_fence_put(&chain->base);
return 0;
}
EXPORT_SYMBOL(dma_fence_chain_find_seqno);
static const char *dma_fence_chain_get_driver_name(struct dma_fence *fence)
{
return "dma_fence_chain";
}
static const char *dma_fence_chain_get_timeline_name(struct dma_fence *fence)
{
return "unbound";
}
static void dma_fence_chain_irq_work(struct irq_work *work)
{
struct dma_fence_chain *chain;
chain = container_of(work, typeof(*chain), work);
/* Try to rearm the callback */
if (!dma_fence_chain_enable_signaling(&chain->base))
/* Ok, we are done. No more unsignaled fences left */
dma_fence_signal(&chain->base);
dma_fence_put(&chain->base);
}
static void dma_fence_chain_cb(struct dma_fence *f, struct dma_fence_cb *cb)
{
struct dma_fence_chain *chain;
chain = container_of(cb, typeof(*chain), cb);
init_irq_work(&chain->work, dma_fence_chain_irq_work);
irq_work_queue(&chain->work);
dma_fence_put(f);
}
static bool dma_fence_chain_enable_signaling(struct dma_fence *fence)
{
struct dma_fence_chain *head = to_dma_fence_chain(fence);
dma_fence_get(&head->base);
dma_fence_chain_for_each(fence, &head->base) {
struct dma_fence *f = dma_fence_chain_contained(fence);
dma_fence_get(f);
if (!dma_fence_add_callback(f, &head->cb, dma_fence_chain_cb)) {
dma_fence_put(fence);
return true;
}
dma_fence_put(f);
}
dma_fence_put(&head->base);
return false;
}
static bool dma_fence_chain_signaled(struct dma_fence *fence)
{
dma_fence_chain_for_each(fence, fence) {
struct dma_fence *f = dma_fence_chain_contained(fence);
if (!dma_fence_is_signaled(f)) {
dma_fence_put(fence);
return false;
}
}
return true;
}
static void dma_fence_chain_release(struct dma_fence *fence)
{
struct dma_fence_chain *chain = to_dma_fence_chain(fence);
struct dma_fence *prev;
/* Manually unlink the chain as much as possible to avoid recursion
* and potential stack overflow.
*/
while ((prev = rcu_dereference_protected(chain->prev, true))) {
struct dma_fence_chain *prev_chain;
if (kref_read(&prev->refcount) > 1)
break;
prev_chain = to_dma_fence_chain(prev);
if (!prev_chain)
break;
/* No need for atomic operations since we hold the last
* reference to prev_chain.
*/
chain->prev = prev_chain->prev;
RCU_INIT_POINTER(prev_chain->prev, NULL);
dma_fence_put(prev);
}
dma_fence_put(prev);
dma_fence_put(chain->fence);
dma_fence_free(fence);
}
static void dma_fence_chain_set_deadline(struct dma_fence *fence,
ktime_t deadline)
{
dma_fence_chain_for_each(fence, fence) {
struct dma_fence *f = dma_fence_chain_contained(fence);
dma_fence_set_deadline(f, deadline);
}
}
const struct dma_fence_ops dma_fence_chain_ops = {
.get_driver_name = dma_fence_chain_get_driver_name,
.get_timeline_name = dma_fence_chain_get_timeline_name,
.enable_signaling = dma_fence_chain_enable_signaling,
.signaled = dma_fence_chain_signaled,
.release = dma_fence_chain_release,
.set_deadline = dma_fence_chain_set_deadline,
};
EXPORT_SYMBOL(dma_fence_chain_ops);
/**
* dma_fence_chain_init - initialize a fence chain
* @chain: the chain node to initialize
* @prev: the previous fence
* @fence: the current fence
* @seqno: the sequence number to use for the fence chain
*
* Initialize a new chain node and either start a new chain or add the node to
* the existing chain of the previous fence.
*/
void dma_fence_chain_init(struct dma_fence_chain *chain,
struct dma_fence *prev,
struct dma_fence *fence,
uint64_t seqno)
{
static struct lock_class_key dma_fence_chain_lock_key;
struct dma_fence_chain *prev_chain = to_dma_fence_chain(prev);
uint64_t context;
rcu_assign_pointer(chain->prev, prev);
chain->fence = fence;
chain->prev_seqno = 0;
/* Try to reuse the context of the previous chain node. */
if (prev_chain && __dma_fence_is_later(prev, seqno, prev->seqno)) {
context = prev->context;
chain->prev_seqno = prev->seqno;
} else {
context = dma_fence_context_alloc(1);
/* Make sure that we always have a valid sequence number. */
if (prev_chain)
seqno = max(prev->seqno, seqno);
}
dma_fence_init64(&chain->base, &dma_fence_chain_ops, NULL,
context, seqno);
/*
* dma_fence_chain_enable_signaling() is invoked while holding
* chain->base.inline_lock and may call dma_fence_add_callback()
* on the underlying fences, which takes their inline_lock.
*
* Since both locks share the same lockdep class, this legitimate
* nesting confuses lockdep and triggers a recursive locking
* warning. Assign a separate lockdep class to the chain lock
* to model this hierarchy correctly.
*/
lockdep_set_class(&chain->base.inline_lock, &dma_fence_chain_lock_key);
/*
* Chaining dma_fence_chain container together is only allowed through
* the prev fence and not through the contained fence.
*
* The correct way of handling this is to flatten out the fence
* structure into a dma_fence_array by the caller instead.
*/
WARN_ON(dma_fence_is_chain(fence));
}
EXPORT_SYMBOL(dma_fence_chain_init);
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* dma-fence-util: misc functions for dma_fence objects
*
* Copyright (C) 2022 Advanced Micro Devices, Inc.
* Authors:
* Christian König <christian.koenig@amd.com>
*/
#include <linux/dma-fence.h>
#include <linux/dma-fence-array.h>
#include <linux/dma-fence-chain.h>
#include <linux/dma-fence-unwrap.h>
#include <linux/slab.h>
#include <linux/sort.h>
/* Internal helper to start new array iteration, don't use directly */
static struct dma_fence *
__dma_fence_unwrap_array(struct dma_fence_unwrap *cursor)
{
cursor->array = dma_fence_chain_contained(cursor->chain);
cursor->index = 0;
return dma_fence_array_first(cursor->array);
}
/**
* dma_fence_unwrap_first - return the first fence from fence containers
* @head: the entrypoint into the containers
* @cursor: current position inside the containers
*
* Unwraps potential dma_fence_chain/dma_fence_array containers and return the
* first fence.
*/
struct dma_fence *dma_fence_unwrap_first(struct dma_fence *head,
struct dma_fence_unwrap *cursor)
{
cursor->chain = dma_fence_get(head);
return __dma_fence_unwrap_array(cursor);
}
EXPORT_SYMBOL_GPL(dma_fence_unwrap_first);
/**
* dma_fence_unwrap_next - return the next fence from a fence containers
* @cursor: current position inside the containers
*
* Continue unwrapping the dma_fence_chain/dma_fence_array containers and return
* the next fence from them.
*/
struct dma_fence *dma_fence_unwrap_next(struct dma_fence_unwrap *cursor)
{
struct dma_fence *tmp;
++cursor->index;
tmp = dma_fence_array_next(cursor->array, cursor->index);
if (tmp)
return tmp;
cursor->chain = dma_fence_chain_walk(cursor->chain);
return __dma_fence_unwrap_array(cursor);
}
EXPORT_SYMBOL_GPL(dma_fence_unwrap_next);
static int fence_cmp(const void *_a, const void *_b)
{
struct dma_fence *a = *(struct dma_fence **)_a;
struct dma_fence *b = *(struct dma_fence **)_b;
if (a->context < b->context)
return -1;
else if (a->context > b->context)
return 1;
if (dma_fence_is_later(b, a))
return 1;
else if (dma_fence_is_later(a, b))
return -1;
return 0;
}
/**
* dma_fence_dedup_array - Sort and deduplicate an array of dma_fence pointers
* @fences: Array of dma_fence pointers to be deduplicated
* @num_fences: Number of entries in the @fences array
*
* Sorts the input array by context, then removes duplicate
* fences with the same context, keeping only the most recent one.
*
* The array is modified in-place and unreferenced duplicate fences are released
* via dma_fence_put(). The function returns the new number of fences after
* deduplication.
*
* Return: Number of unique fences remaining in the array.
*/
int dma_fence_dedup_array(struct dma_fence **fences, int num_fences)
{
int i, j;
if (!num_fences)
return 0;
sort(fences, num_fences, sizeof(*fences), fence_cmp, NULL);
/*
* Only keep the most recent fence for each context.
*/
j = 0;
for (i = 1; i < num_fences; i++) {
if (fences[i]->context == fences[j]->context)
dma_fence_put(fences[i]);
else
fences[++j] = fences[i];
}
return ++j;
}
EXPORT_SYMBOL_GPL(dma_fence_dedup_array);
/* Implementation for the dma_fence_merge() marco, don't use directly */
struct dma_fence *__dma_fence_unwrap_merge(unsigned int num_fences,
struct dma_fence **fences,
struct dma_fence_unwrap *iter)
{
struct dma_fence *tmp, *unsignaled = NULL, **array;
struct dma_fence_array *result;
ktime_t timestamp;
int i, count;
count = 0;
timestamp = ns_to_ktime(0);
for (i = 0; i < num_fences; ++i) {
dma_fence_unwrap_for_each(tmp, &iter[i], fences[i]) {
if (!dma_fence_is_signaled(tmp)) {
dma_fence_put(unsignaled);
unsignaled = dma_fence_get(tmp);
++count;
} else {
ktime_t t = dma_fence_timestamp(tmp);
if (ktime_after(t, timestamp))
timestamp = t;
}
}
}
/*
* If we couldn't find a pending fence just return a private signaled
* fence with the timestamp of the last signaled one.
*
* Or if there was a single unsignaled fence left we can return it
* directly and early since that is a major path on many workloads.
*/
if (count == 0)
return dma_fence_allocate_private_stub(timestamp);
else if (count == 1)
return unsignaled;
dma_fence_put(unsignaled);
array = kmalloc_objs(*array, count);
if (!array)
return NULL;
count = 0;
for (i = 0; i < num_fences; ++i) {
dma_fence_unwrap_for_each(tmp, &iter[i], fences[i]) {
if (!dma_fence_is_signaled(tmp)) {
array[count++] = dma_fence_get(tmp);
} else {
ktime_t t = dma_fence_timestamp(tmp);
if (ktime_after(t, timestamp))
timestamp = t;
}
}
}
if (count == 0 || count == 1)
goto return_fastpath;
count = dma_fence_dedup_array(array, count);
if (count > 1) {
result = dma_fence_array_create(count, array,
dma_fence_context_alloc(1), 1);
if (!result) {
for (i = 0; i < count; i++)
dma_fence_put(array[i]);
tmp = NULL;
goto return_tmp;
}
return &result->base;
}
return_fastpath:
if (count == 0)
tmp = dma_fence_allocate_private_stub(timestamp);
else
tmp = array[0];
return_tmp:
kfree(array);
return tmp;
}
EXPORT_SYMBOL_GPL(__dma_fence_unwrap_merge);
File diff suppressed because it is too large Load Diff
+339
View File
@@ -0,0 +1,339 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Framework for userspace DMA-BUF allocations
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2019 Linaro Ltd.
*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/list.h>
#include <linux/nospec.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/xarray.h>
#include <uapi/linux/dma-heap.h>
#define DEVNAME "dma_heap"
#define NUM_HEAP_MINORS 128
/**
* struct dma_heap - represents a dmabuf heap in the system
* @name: used for debugging/device-node name
* @ops: ops struct for this heap
* @priv: private data for this heap
* @heap_devt: heap device node
* @list: list head connecting to list of heaps
* @heap_cdev: heap char device
*
* Represents a heap of memory from which buffers can be made.
*/
struct dma_heap {
const char *name;
const struct dma_heap_ops *ops;
void *priv;
dev_t heap_devt;
struct list_head list;
struct cdev heap_cdev;
};
static LIST_HEAD(heap_list);
static DEFINE_MUTEX(heap_list_lock);
static dev_t dma_heap_devt;
static struct class *dma_heap_class;
static DEFINE_XARRAY_ALLOC(dma_heap_minors);
bool __read_mostly mem_accounting;
module_param(mem_accounting, bool, 0444);
MODULE_PARM_DESC(mem_accounting,
"Enable cgroup-based memory accounting for dma-buf heap allocations (default=false).");
EXPORT_SYMBOL_NS_GPL(mem_accounting, "DMA_BUF_HEAP");
static int dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
u32 fd_flags,
u64 heap_flags)
{
struct dma_buf *dmabuf;
int fd;
/*
* Allocations from all heaps have to begin
* and end on page boundaries.
*/
len = PAGE_ALIGN(len);
if (!len)
return -EINVAL;
dmabuf = heap->ops->allocate(heap, len, fd_flags, heap_flags);
if (IS_ERR(dmabuf))
return PTR_ERR(dmabuf);
fd = dma_buf_fd(dmabuf, fd_flags);
if (fd < 0) {
dma_buf_put(dmabuf);
/* just return, as put will call release and that will free */
}
return fd;
}
static int dma_heap_open(struct inode *inode, struct file *file)
{
struct dma_heap *heap;
heap = xa_load(&dma_heap_minors, iminor(inode));
if (!heap) {
pr_err("dma_heap: minor %d unknown.\n", iminor(inode));
return -ENODEV;
}
/* instance data as context */
file->private_data = heap;
nonseekable_open(inode, file);
return 0;
}
static long dma_heap_ioctl_allocate(struct file *file, void *data)
{
struct dma_heap_allocation_data *heap_allocation = data;
struct dma_heap *heap = file->private_data;
int fd;
if (heap_allocation->fd)
return -EINVAL;
if (heap_allocation->fd_flags & ~DMA_HEAP_VALID_FD_FLAGS)
return -EINVAL;
if (heap_allocation->heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
return -EINVAL;
fd = dma_heap_buffer_alloc(heap, heap_allocation->len,
heap_allocation->fd_flags,
heap_allocation->heap_flags);
if (fd < 0)
return fd;
heap_allocation->fd = fd;
return 0;
}
static unsigned int dma_heap_ioctl_cmds[] = {
DMA_HEAP_IOCTL_ALLOC,
};
static long dma_heap_ioctl(struct file *file, unsigned int ucmd,
unsigned long arg)
{
char stack_kdata[128];
char *kdata = stack_kdata;
unsigned int kcmd;
unsigned int in_size, out_size, drv_size, ksize;
int nr = _IOC_NR(ucmd);
int ret = 0;
if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds))
return -EINVAL;
nr = array_index_nospec(nr, ARRAY_SIZE(dma_heap_ioctl_cmds));
/* Get the kernel ioctl cmd that matches */
kcmd = dma_heap_ioctl_cmds[nr];
/* Figure out the delta between user cmd size and kernel cmd size */
drv_size = _IOC_SIZE(kcmd);
out_size = _IOC_SIZE(ucmd);
in_size = out_size;
if ((ucmd & kcmd & IOC_IN) == 0)
in_size = 0;
if ((ucmd & kcmd & IOC_OUT) == 0)
out_size = 0;
ksize = max(max(in_size, out_size), drv_size);
/* If necessary, allocate buffer for ioctl argument */
if (ksize > sizeof(stack_kdata)) {
kdata = kmalloc(ksize, GFP_KERNEL);
if (!kdata)
return -ENOMEM;
}
if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) {
ret = -EFAULT;
goto err;
}
/* zero out any difference between the kernel/user structure size */
if (ksize > in_size)
memset(kdata + in_size, 0, ksize - in_size);
switch (kcmd) {
case DMA_HEAP_IOCTL_ALLOC:
ret = dma_heap_ioctl_allocate(file, kdata);
break;
default:
ret = -ENOTTY;
goto err;
}
if (copy_to_user((void __user *)arg, kdata, out_size) != 0)
ret = -EFAULT;
err:
if (kdata != stack_kdata)
kfree(kdata);
return ret;
}
static const struct file_operations dma_heap_fops = {
.owner = THIS_MODULE,
.open = dma_heap_open,
.unlocked_ioctl = dma_heap_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = dma_heap_ioctl,
#endif
};
/**
* dma_heap_get_drvdata - get per-heap driver data
* @heap: DMA-Heap to retrieve private data for
*
* Returns:
* The per-heap data for the heap.
*/
void *dma_heap_get_drvdata(struct dma_heap *heap)
{
return heap->priv;
}
EXPORT_SYMBOL_NS_GPL(dma_heap_get_drvdata, "DMA_BUF_HEAP");
/**
* dma_heap_get_name - get heap name
* @heap: DMA-Heap to retrieve the name of
*
* Returns:
* The char* for the heap name.
*/
const char *dma_heap_get_name(struct dma_heap *heap)
{
return heap->name;
}
EXPORT_SYMBOL_NS_GPL(dma_heap_get_name, "DMA_BUF_HEAP");
/**
* dma_heap_add - adds a heap to dmabuf heaps
* @exp_info: information needed to register this heap
*/
struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info)
{
struct dma_heap *heap, *h, *err_ret;
struct device *dev_ret;
unsigned int minor;
int ret;
if (!exp_info->name || !strcmp(exp_info->name, "")) {
pr_err("dma_heap: Cannot add heap without a name\n");
return ERR_PTR(-EINVAL);
}
if (!exp_info->ops || !exp_info->ops->allocate) {
pr_err("dma_heap: Cannot add heap with invalid ops struct\n");
return ERR_PTR(-EINVAL);
}
heap = kzalloc_obj(*heap);
if (!heap)
return ERR_PTR(-ENOMEM);
heap->name = exp_info->name;
heap->ops = exp_info->ops;
heap->priv = exp_info->priv;
/* Find unused minor number */
ret = xa_alloc(&dma_heap_minors, &minor, heap,
XA_LIMIT(0, NUM_HEAP_MINORS - 1), GFP_KERNEL);
if (ret < 0) {
pr_err("dma_heap: Unable to get minor number for heap\n");
err_ret = ERR_PTR(ret);
goto err0;
}
/* Create device */
heap->heap_devt = MKDEV(MAJOR(dma_heap_devt), minor);
cdev_init(&heap->heap_cdev, &dma_heap_fops);
ret = cdev_add(&heap->heap_cdev, heap->heap_devt, 1);
if (ret < 0) {
pr_err("dma_heap: Unable to add char device\n");
err_ret = ERR_PTR(ret);
goto err1;
}
dev_ret = device_create(dma_heap_class,
NULL,
heap->heap_devt,
NULL,
heap->name);
if (IS_ERR(dev_ret)) {
pr_err("dma_heap: Unable to create device\n");
err_ret = ERR_CAST(dev_ret);
goto err2;
}
mutex_lock(&heap_list_lock);
/* check the name is unique */
list_for_each_entry(h, &heap_list, list) {
if (!strcmp(h->name, exp_info->name)) {
mutex_unlock(&heap_list_lock);
pr_err("dma_heap: Already registered heap named %s\n",
exp_info->name);
err_ret = ERR_PTR(-EINVAL);
goto err3;
}
}
/* Add heap to the list */
list_add(&heap->list, &heap_list);
mutex_unlock(&heap_list_lock);
return heap;
err3:
device_destroy(dma_heap_class, heap->heap_devt);
err2:
cdev_del(&heap->heap_cdev);
err1:
xa_erase(&dma_heap_minors, minor);
err0:
kfree(heap);
return err_ret;
}
EXPORT_SYMBOL_NS_GPL(dma_heap_add, "DMA_BUF_HEAP");
static char *dma_heap_devnode(const struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "dma_heap/%s", dev_name(dev));
}
static int dma_heap_init(void)
{
int ret;
ret = alloc_chrdev_region(&dma_heap_devt, 0, NUM_HEAP_MINORS, DEVNAME);
if (ret)
return ret;
dma_heap_class = class_create(DEVNAME);
if (IS_ERR(dma_heap_class)) {
unregister_chrdev_region(dma_heap_devt, NUM_HEAP_MINORS);
return PTR_ERR(dma_heap_class);
}
dma_heap_class->devnode = dma_heap_devnode;
return 0;
}
subsys_initcall(dma_heap_init);
+818
View File
@@ -0,0 +1,818 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2012-2014 Canonical Ltd (Maarten Lankhorst)
*
* Based on bo.c which bears the following copyright notice,
* but is dual licensed:
*
* Copyright (c) 2006-2009 VMware, Inc., Palo Alto, CA., USA
* All Rights Reserved.
*
* 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, sub license, 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 NON-INFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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: Thomas Hellstrom <thellstrom-at-vmware-dot-com>
*/
#include <linux/dma-resv.h>
#include <linux/dma-fence-array.h>
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/sched/mm.h>
#include <linux/mmu_notifier.h>
#include <linux/seq_file.h>
/**
* DOC: Reservation Object Overview
*
* The reservation object provides a mechanism to manage a container of
* dma_fence object associated with a resource. A reservation object
* can have any number of fences attaches to it. Each fence carries an usage
* parameter determining how the operation represented by the fence is using the
* resource. The RCU mechanism is used to protect read access to fences from
* locked write-side updates.
*
* See struct dma_resv for more details.
*/
DEFINE_WD_CLASS(reservation_ww_class);
EXPORT_SYMBOL(reservation_ww_class);
/* Mask for the lower fence pointer bits */
#define DMA_RESV_LIST_MASK 0x3
struct dma_resv_list {
struct rcu_head rcu;
u32 num_fences, max_fences;
struct dma_fence __rcu *table[];
};
/* Extract the fence and usage flags from an RCU protected entry in the list. */
static void dma_resv_list_entry(struct dma_resv_list *list, unsigned int index,
struct dma_resv *resv, struct dma_fence **fence,
enum dma_resv_usage *usage)
{
long tmp;
tmp = (long)rcu_dereference_check(list->table[index],
resv ? dma_resv_held(resv) : true);
*fence = (struct dma_fence *)(tmp & ~DMA_RESV_LIST_MASK);
if (usage)
*usage = tmp & DMA_RESV_LIST_MASK;
}
/* Set the fence and usage flags at the specific index in the list. */
static void dma_resv_list_set(struct dma_resv_list *list,
unsigned int index,
struct dma_fence *fence,
enum dma_resv_usage usage)
{
long tmp = ((long)fence) | usage;
RCU_INIT_POINTER(list->table[index], (struct dma_fence *)tmp);
}
/*
* Allocate a new dma_resv_list and make sure to correctly initialize
* max_fences.
*/
static struct dma_resv_list *dma_resv_list_alloc(unsigned int max_fences)
{
struct dma_resv_list *list;
size_t size;
/* Round up to the next kmalloc bucket size. */
size = kmalloc_size_roundup(struct_size(list, table, max_fences));
list = kmalloc(size, GFP_KERNEL);
if (!list)
return NULL;
/* Given the resulting bucket size, recalculated max_fences. */
list->max_fences = (size - offsetof(typeof(*list), table)) /
sizeof(*list->table);
return list;
}
/* Free a dma_resv_list and make sure to drop all references. */
static void dma_resv_list_free(struct dma_resv_list *list)
{
unsigned int i;
if (!list)
return;
for (i = 0; i < list->num_fences; ++i) {
struct dma_fence *fence;
dma_resv_list_entry(list, i, NULL, &fence, NULL);
dma_fence_put(fence);
}
kfree_rcu(list, rcu);
}
/**
* dma_resv_init - initialize a reservation object
* @obj: the reservation object
*/
void dma_resv_init(struct dma_resv *obj)
{
ww_mutex_init(&obj->lock, &reservation_ww_class);
RCU_INIT_POINTER(obj->fences, NULL);
}
EXPORT_SYMBOL(dma_resv_init);
/**
* dma_resv_fini - destroys a reservation object
* @obj: the reservation object
*/
void dma_resv_fini(struct dma_resv *obj)
{
/*
* This object should be dead and all references must have
* been released to it, so no need to be protected with rcu.
*/
dma_resv_list_free(rcu_dereference_protected(obj->fences, true));
ww_mutex_destroy(&obj->lock);
}
EXPORT_SYMBOL(dma_resv_fini);
/* Dereference the fences while ensuring RCU rules */
static inline struct dma_resv_list *dma_resv_fences_list(struct dma_resv *obj)
{
return rcu_dereference_check(obj->fences, dma_resv_held(obj));
}
/**
* dma_resv_reserve_fences - Reserve space to add fences to a dma_resv object.
* @obj: reservation object
* @num_fences: number of fences we want to add
*
* Should be called before dma_resv_add_fence(). Must be called with @obj
* locked through dma_resv_lock().
*
* Note that the preallocated slots need to be re-reserved if @obj is unlocked
* at any time before calling dma_resv_add_fence(). This is validated when
* CONFIG_DEBUG_MUTEXES is enabled.
*
* RETURNS
* Zero for success, or -errno
*/
int dma_resv_reserve_fences(struct dma_resv *obj, unsigned int num_fences)
{
struct dma_resv_list *old, *new;
unsigned int i, j, k, max;
dma_resv_assert_held(obj);
/* Driver and component code should never call this function with
* num_fences=0. If they do it usually points to bugs when calculating
* the number of needed fences dynamically.
*/
if (WARN_ON(!num_fences))
return -EINVAL;
old = dma_resv_fences_list(obj);
if (old && old->max_fences) {
if ((old->num_fences + num_fences) <= old->max_fences)
return 0;
max = max(old->num_fences + num_fences, old->max_fences * 2);
} else {
max = max(4ul, roundup_pow_of_two(num_fences));
}
new = dma_resv_list_alloc(max);
if (!new)
return -ENOMEM;
/*
* no need to bump fence refcounts, rcu_read access
* requires the use of kref_get_unless_zero, and the
* references from the old struct are carried over to
* the new.
*/
for (i = 0, j = 0, k = max; i < (old ? old->num_fences : 0); ++i) {
enum dma_resv_usage usage;
struct dma_fence *fence;
dma_resv_list_entry(old, i, obj, &fence, &usage);
if (dma_fence_is_signaled(fence))
RCU_INIT_POINTER(new->table[--k], fence);
else
dma_resv_list_set(new, j++, fence, usage);
}
new->num_fences = j;
/*
* We are not changing the effective set of fences here so can
* merely update the pointer to the new array; both existing
* readers and new readers will see exactly the same set of
* active (unsignaled) fences. Individual fences and the
* old array are protected by RCU and so will not vanish under
* the gaze of the rcu_read_lock() readers.
*/
rcu_assign_pointer(obj->fences, new);
if (!old)
return 0;
/* Drop the references to the signaled fences */
for (i = k; i < max; ++i) {
struct dma_fence *fence;
fence = rcu_dereference_protected(new->table[i],
dma_resv_held(obj));
dma_fence_put(fence);
}
kfree_rcu(old, rcu);
return 0;
}
EXPORT_SYMBOL(dma_resv_reserve_fences);
#ifdef CONFIG_DEBUG_MUTEXES
/**
* dma_resv_reset_max_fences - reset fences for debugging
* @obj: the dma_resv object to reset
*
* Reset the number of pre-reserved fence slots to test that drivers do
* correct slot allocation using dma_resv_reserve_fences(). See also
* &dma_resv_list.max_fences.
*/
void dma_resv_reset_max_fences(struct dma_resv *obj)
{
struct dma_resv_list *fences = dma_resv_fences_list(obj);
dma_resv_assert_held(obj);
/* Test fence slot reservation */
if (fences)
fences->max_fences = fences->num_fences;
}
EXPORT_SYMBOL(dma_resv_reset_max_fences);
#endif
/**
* dma_resv_add_fence - Add a fence to the dma_resv obj
* @obj: the reservation object
* @fence: the fence to add
* @usage: how the fence is used, see enum dma_resv_usage
*
* Add a fence to a slot, @obj must be locked with dma_resv_lock(), and
* dma_resv_reserve_fences() has been called.
*
* See also &dma_resv.fence for a discussion of the semantics.
*/
void dma_resv_add_fence(struct dma_resv *obj, struct dma_fence *fence,
enum dma_resv_usage usage)
{
struct dma_resv_list *fobj;
struct dma_fence *old;
unsigned int i, count;
dma_fence_get(fence);
dma_resv_assert_held(obj);
/* Drivers should not add containers here, instead add each fence
* individually.
*/
WARN_ON(dma_fence_is_container(fence));
fobj = dma_resv_fences_list(obj);
count = fobj->num_fences;
for (i = 0; i < count; ++i) {
enum dma_resv_usage old_usage;
dma_resv_list_entry(fobj, i, obj, &old, &old_usage);
if ((old->context == fence->context && old_usage >= usage &&
dma_fence_is_later_or_same(fence, old)) ||
dma_fence_is_signaled(old)) {
dma_resv_list_set(fobj, i, fence, usage);
dma_fence_put(old);
return;
}
}
BUG_ON(fobj->num_fences >= fobj->max_fences);
count++;
dma_resv_list_set(fobj, i, fence, usage);
/* fence update must be visible before we extend the num_fences */
smp_wmb();
fobj->num_fences = count;
}
EXPORT_SYMBOL(dma_resv_add_fence);
/**
* dma_resv_replace_fences - replace fences in the dma_resv obj
* @obj: the reservation object
* @context: the context of the fences to replace
* @replacement: the new fence to use instead
* @usage: how the new fence is used, see enum dma_resv_usage
*
* Replace fences with a specified context with a new fence. Only valid if the
* operation represented by the original fence has no longer access to the
* resources represented by the dma_resv object when the new fence completes.
*
* And example for using this is replacing a preemption fence with a page table
* update fence which makes the resource inaccessible.
*/
void dma_resv_replace_fences(struct dma_resv *obj, uint64_t context,
struct dma_fence *replacement,
enum dma_resv_usage usage)
{
struct dma_resv_list *list;
unsigned int i;
dma_resv_assert_held(obj);
list = dma_resv_fences_list(obj);
for (i = 0; list && i < list->num_fences; ++i) {
struct dma_fence *old;
dma_resv_list_entry(list, i, obj, &old, NULL);
if (old->context != context)
continue;
dma_resv_list_set(list, i, dma_fence_get(replacement), usage);
dma_fence_put(old);
}
}
EXPORT_SYMBOL(dma_resv_replace_fences);
/* Restart the unlocked iteration by initializing the cursor object. */
static void dma_resv_iter_restart_unlocked(struct dma_resv_iter *cursor)
{
cursor->index = 0;
cursor->num_fences = 0;
cursor->fences = dma_resv_fences_list(cursor->obj);
if (cursor->fences)
cursor->num_fences = cursor->fences->num_fences;
cursor->is_restarted = true;
}
/* Walk to the next not signaled fence and grab a reference to it */
static void dma_resv_iter_walk_unlocked(struct dma_resv_iter *cursor)
{
if (!cursor->fences)
return;
do {
/* Drop the reference from the previous round */
dma_fence_put(cursor->fence);
if (cursor->index >= cursor->num_fences) {
cursor->fence = NULL;
break;
}
dma_resv_list_entry(cursor->fences, cursor->index++,
cursor->obj, &cursor->fence,
&cursor->fence_usage);
cursor->fence = dma_fence_get_rcu(cursor->fence);
if (!cursor->fence) {
dma_resv_iter_restart_unlocked(cursor);
continue;
}
if (!dma_fence_is_signaled(cursor->fence) &&
cursor->usage >= cursor->fence_usage)
break;
} while (true);
}
/**
* dma_resv_iter_first_unlocked - first fence in an unlocked dma_resv obj.
* @cursor: the cursor with the current position
*
* Subsequent fences are iterated with dma_resv_iter_next_unlocked().
*
* Beware that the iterator can be restarted. Code which accumulates statistics
* or similar needs to check for this with dma_resv_iter_is_restarted(). For
* this reason prefer the locked dma_resv_iter_first() whenever possible.
*
* Returns the first fence from an unlocked dma_resv obj.
*/
struct dma_fence *dma_resv_iter_first_unlocked(struct dma_resv_iter *cursor)
{
rcu_read_lock();
do {
dma_resv_iter_restart_unlocked(cursor);
dma_resv_iter_walk_unlocked(cursor);
} while (dma_resv_fences_list(cursor->obj) != cursor->fences);
rcu_read_unlock();
return cursor->fence;
}
EXPORT_SYMBOL(dma_resv_iter_first_unlocked);
/**
* dma_resv_iter_next_unlocked - next fence in an unlocked dma_resv obj.
* @cursor: the cursor with the current position
*
* Beware that the iterator can be restarted. Code which accumulates statistics
* or similar needs to check for this with dma_resv_iter_is_restarted(). For
* this reason prefer the locked dma_resv_iter_next() whenever possible.
*
* Returns the next fence from an unlocked dma_resv obj.
*/
struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter *cursor)
{
bool restart;
rcu_read_lock();
cursor->is_restarted = false;
restart = dma_resv_fences_list(cursor->obj) != cursor->fences;
do {
if (restart)
dma_resv_iter_restart_unlocked(cursor);
dma_resv_iter_walk_unlocked(cursor);
restart = true;
} while (dma_resv_fences_list(cursor->obj) != cursor->fences);
rcu_read_unlock();
return cursor->fence;
}
EXPORT_SYMBOL(dma_resv_iter_next_unlocked);
/**
* dma_resv_iter_first - first fence from a locked dma_resv object
* @cursor: cursor to record the current position
*
* Subsequent fences are iterated with dma_resv_iter_next_unlocked().
*
* Return the first fence in the dma_resv object while holding the
* &dma_resv.lock.
*/
struct dma_fence *dma_resv_iter_first(struct dma_resv_iter *cursor)
{
struct dma_fence *fence;
dma_resv_assert_held(cursor->obj);
cursor->index = 0;
cursor->fences = dma_resv_fences_list(cursor->obj);
fence = dma_resv_iter_next(cursor);
cursor->is_restarted = true;
return fence;
}
EXPORT_SYMBOL_GPL(dma_resv_iter_first);
/**
* dma_resv_iter_next - next fence from a locked dma_resv object
* @cursor: cursor to record the current position
*
* Return the next fences from the dma_resv object while holding the
* &dma_resv.lock.
*/
struct dma_fence *dma_resv_iter_next(struct dma_resv_iter *cursor)
{
struct dma_fence *fence;
dma_resv_assert_held(cursor->obj);
cursor->is_restarted = false;
do {
if (!cursor->fences ||
cursor->index >= cursor->fences->num_fences)
return NULL;
dma_resv_list_entry(cursor->fences, cursor->index++,
cursor->obj, &fence, &cursor->fence_usage);
} while (cursor->fence_usage > cursor->usage);
return fence;
}
EXPORT_SYMBOL_GPL(dma_resv_iter_next);
/**
* dma_resv_copy_fences - Copy all fences from src to dst.
* @dst: the destination reservation object
* @src: the source reservation object
*
* Copy all fences from src to dst. dst-lock must be held.
*/
int dma_resv_copy_fences(struct dma_resv *dst, struct dma_resv *src)
{
struct dma_resv_iter cursor;
struct dma_resv_list *list;
struct dma_fence *f;
dma_resv_assert_held(dst);
list = NULL;
dma_resv_iter_begin(&cursor, src, DMA_RESV_USAGE_BOOKKEEP);
dma_resv_for_each_fence_unlocked(&cursor, f) {
if (dma_resv_iter_is_restarted(&cursor)) {
dma_resv_list_free(list);
list = dma_resv_list_alloc(cursor.num_fences);
if (!list) {
dma_resv_iter_end(&cursor);
return -ENOMEM;
}
list->num_fences = 0;
}
dma_fence_get(f);
dma_resv_list_set(list, list->num_fences++, f,
dma_resv_iter_usage(&cursor));
}
dma_resv_iter_end(&cursor);
list = rcu_replace_pointer(dst->fences, list, dma_resv_held(dst));
dma_resv_list_free(list);
return 0;
}
EXPORT_SYMBOL(dma_resv_copy_fences);
/**
* dma_resv_get_fences - Get an object's fences
* fences without update side lock held
* @obj: the reservation object
* @usage: controls which fences to include, see enum dma_resv_usage.
* @num_fences: the number of fences returned
* @fences: the array of fence ptrs returned (array is krealloc'd to the
* required size, and must be freed by caller)
*
* Retrieve all fences from the reservation object.
* Returns either zero or -ENOMEM.
*/
int dma_resv_get_fences(struct dma_resv *obj, enum dma_resv_usage usage,
unsigned int *num_fences, struct dma_fence ***fences)
{
struct dma_resv_iter cursor;
struct dma_fence *fence;
*num_fences = 0;
*fences = NULL;
dma_resv_iter_begin(&cursor, obj, usage);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
if (dma_resv_iter_is_restarted(&cursor)) {
struct dma_fence **new_fences;
unsigned int count;
while (*num_fences)
dma_fence_put((*fences)[--(*num_fences)]);
count = cursor.num_fences + 1;
/* Eventually re-allocate the array */
new_fences = krealloc_array(*fences, count,
sizeof(void *),
GFP_KERNEL);
if (count && !new_fences) {
kfree(*fences);
*fences = NULL;
*num_fences = 0;
dma_resv_iter_end(&cursor);
return -ENOMEM;
}
*fences = new_fences;
}
(*fences)[(*num_fences)++] = dma_fence_get(fence);
}
dma_resv_iter_end(&cursor);
return 0;
}
EXPORT_SYMBOL_GPL(dma_resv_get_fences);
/**
* dma_resv_get_singleton - Get a single fence for all the fences
* @obj: the reservation object
* @usage: controls which fences to include, see enum dma_resv_usage.
* @fence: the resulting fence
*
* Get a single fence representing all the fences inside the resv object.
* Returns either 0 for success or -ENOMEM.
*
* Warning: This can't be used like this when adding the fence back to the resv
* object since that can lead to stack corruption when finalizing the
* dma_fence_array.
*
* Returns 0 on success and negative error values on failure.
*/
int dma_resv_get_singleton(struct dma_resv *obj, enum dma_resv_usage usage,
struct dma_fence **fence)
{
struct dma_fence_array *array;
struct dma_fence **fences;
unsigned count;
int r;
r = dma_resv_get_fences(obj, usage, &count, &fences);
if (r)
return r;
if (count == 0) {
*fence = NULL;
return 0;
}
if (count == 1) {
*fence = fences[0];
kfree(fences);
return 0;
}
array = dma_fence_array_create(count, fences,
dma_fence_context_alloc(1), 1);
if (!array) {
while (count--)
dma_fence_put(fences[count]);
kfree(fences);
return -ENOMEM;
}
*fence = &array->base;
return 0;
}
EXPORT_SYMBOL_GPL(dma_resv_get_singleton);
/**
* dma_resv_wait_timeout - Wait on reservation's objects fences
* @obj: the reservation object
* @usage: controls which fences to include, see enum dma_resv_usage.
* @intr: if true, do interruptible wait
* @timeout: timeout value in jiffies or zero to return immediately
*
* Callers are not required to hold specific locks, but maybe hold
* dma_resv_lock() already
* RETURNS
* Returns -ERESTARTSYS if interrupted, 0 if the wait timed out, or
* greater than zero on success.
*/
long dma_resv_wait_timeout(struct dma_resv *obj, enum dma_resv_usage usage,
bool intr, unsigned long timeout)
{
long ret = timeout ? timeout : 1;
struct dma_resv_iter cursor;
struct dma_fence *fence;
dma_resv_iter_begin(&cursor, obj, usage);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
ret = dma_fence_wait_timeout(fence, intr, timeout);
if (ret <= 0)
break;
/* Even for zero timeout the return value is 1 */
if (timeout)
timeout = ret;
}
dma_resv_iter_end(&cursor);
return ret;
}
EXPORT_SYMBOL_GPL(dma_resv_wait_timeout);
/**
* dma_resv_set_deadline - Set a deadline on reservation's objects fences
* @obj: the reservation object
* @usage: controls which fences to include, see enum dma_resv_usage.
* @deadline: the requested deadline (MONOTONIC)
*
* May be called without holding the dma_resv lock. Sets @deadline on
* all fences filtered by @usage.
*/
void dma_resv_set_deadline(struct dma_resv *obj, enum dma_resv_usage usage,
ktime_t deadline)
{
struct dma_resv_iter cursor;
struct dma_fence *fence;
dma_resv_iter_begin(&cursor, obj, usage);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
dma_fence_set_deadline(fence, deadline);
}
dma_resv_iter_end(&cursor);
}
EXPORT_SYMBOL_GPL(dma_resv_set_deadline);
/**
* dma_resv_test_signaled - Test if a reservation object's fences have been
* signaled.
* @obj: the reservation object
* @usage: controls which fences to include, see enum dma_resv_usage.
*
* Callers are not required to hold specific locks, but maybe hold
* dma_resv_lock() already.
*
* RETURNS
*
* True if all fences signaled, else false.
*/
bool dma_resv_test_signaled(struct dma_resv *obj, enum dma_resv_usage usage)
{
struct dma_resv_iter cursor;
struct dma_fence *fence;
dma_resv_iter_begin(&cursor, obj, usage);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
dma_resv_iter_end(&cursor);
return false;
}
dma_resv_iter_end(&cursor);
return true;
}
EXPORT_SYMBOL_GPL(dma_resv_test_signaled);
/**
* dma_resv_describe - Dump description of the resv object into seq_file
* @obj: the reservation object
* @seq: the seq_file to dump the description into
*
* Dump a textual description of the fences inside an dma_resv object into the
* seq_file.
*/
void dma_resv_describe(struct dma_resv *obj, struct seq_file *seq)
{
static const char *usage[] = { "kernel", "write", "read", "bookkeep" };
struct dma_resv_iter cursor;
struct dma_fence *fence;
dma_resv_for_each_fence(&cursor, obj, DMA_RESV_USAGE_READ, fence) {
seq_printf(seq, "\t%s fence:",
usage[dma_resv_iter_usage(&cursor)]);
dma_fence_describe(fence, seq);
}
}
EXPORT_SYMBOL_GPL(dma_resv_describe);
#if IS_ENABLED(CONFIG_LOCKDEP)
static int __init dma_resv_lockdep(void)
{
struct mm_struct *mm = mm_alloc();
struct ww_acquire_ctx ctx;
struct dma_resv obj;
struct address_space mapping;
int ret;
if (!mm)
return -ENOMEM;
dma_resv_init(&obj);
address_space_init_once(&mapping);
mmap_read_lock(mm);
ww_acquire_init(&ctx, &reservation_ww_class);
ret = dma_resv_lock(&obj, &ctx);
if (ret) {
/* Only EDEADLK from the error injection is possible here */
WARN_ON(ret != -EDEADLK);
dma_resv_lock_slow(&obj, &ctx);
}
fs_reclaim_acquire(GFP_KERNEL);
/* for unmap_mapping_range on trylocked buffer objects in shrinkers */
i_mmap_lock_write(&mapping);
i_mmap_unlock_write(&mapping);
#ifdef CONFIG_MMU_NOTIFIER
lock_map_acquire(&__mmu_notifier_invalidate_range_start_map);
__dma_fence_might_wait();
lock_map_release(&__mmu_notifier_invalidate_range_start_map);
#else
__dma_fence_might_wait();
#endif
fs_reclaim_release(GFP_KERNEL);
ww_mutex_unlock(&obj.lock);
ww_acquire_fini(&ctx);
mmap_read_unlock(mm);
mmput(mm);
return 0;
}
subsys_initcall(dma_resv_lockdep);
#endif
+22
View File
@@ -0,0 +1,22 @@
config DMABUF_HEAPS_SYSTEM
tristate "DMA-BUF System Heap"
depends on DMABUF_HEAPS
help
Choose this option to enable the system dmabuf heap. The system heap
is backed by pages from the buddy allocator. If in doubt, say Y.
config DMABUF_HEAPS_SYSTEM_CC_SHARED
bool "DMA-BUF System Heap for decrypted CoCo VMs"
depends on DMABUF_HEAPS && ARCH_HAS_MEM_ENCRYPT && DMABUF_HEAPS_SYSTEM=y
help
Choose this option to enable the system_cc_shared dmabuf heap. This
allows allocating shared (decrypted) memory for confidential computing
(CoCo) VMs.
config DMABUF_HEAPS_CMA
tristate "DMA-BUF CMA Heap"
depends on DMABUF_HEAPS && DMA_CMA
help
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.
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DMABUF_HEAPS_SYSTEM) += system_heap.o
obj-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o
@@ -0,0 +1,425 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DMABUF CMA heap exporter
*
* Copyright (C) 2012, 2019, 2020 Linaro Ltd.
* Author: <benjamin.gaignard@linaro.org> for ST-Ericsson.
*
* Also utilizing parts of Andrew Davis' SRAM heap:
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*/
#define pr_fmt(fmt) "cma_heap: " fmt
#include <linux/cma.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/dma-map-ops.h>
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#define DEFAULT_CMA_NAME "default_cma_region"
struct cma_heap {
struct dma_heap *heap;
struct cma *cma;
};
struct cma_heap_buffer {
struct cma_heap *heap;
struct list_head attachments;
struct mutex lock;
unsigned long len;
struct page *cma_pages;
struct page **pages;
pgoff_t pagecount;
int vmap_cnt;
void *vaddr;
};
struct dma_heap_attachment {
struct device *dev;
struct sg_table table;
struct list_head list;
bool mapped;
};
static int cma_heap_attach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
int ret;
a = kzalloc_obj(*a);
if (!a)
return -ENOMEM;
ret = sg_alloc_table_from_pages(&a->table, buffer->pages,
buffer->pagecount, 0,
buffer->pagecount << PAGE_SHIFT,
GFP_KERNEL);
if (ret) {
kfree(a);
return ret;
}
a->dev = attachment->dev;
INIT_LIST_HEAD(&a->list);
a->mapped = false;
attachment->priv = a;
mutex_lock(&buffer->lock);
list_add(&a->list, &buffer->attachments);
mutex_unlock(&buffer->lock);
return 0;
}
static void cma_heap_detach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a = attachment->priv;
mutex_lock(&buffer->lock);
list_del(&a->list);
mutex_unlock(&buffer->lock);
sg_free_table(&a->table);
kfree(a);
}
static struct sg_table *cma_heap_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
struct sg_table *table = &a->table;
int ret;
ret = dma_map_sgtable(attachment->dev, table, direction, 0);
if (ret)
return ERR_PTR(-ENOMEM);
a->mapped = true;
return table;
}
static void cma_heap_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
a->mapped = false;
dma_unmap_sgtable(attachment->dev, table, direction, 0);
}
static int cma_heap_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
invalidate_kernel_vmap_range(buffer->vaddr, buffer->len);
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_cpu(a->dev, &a->table, direction);
}
mutex_unlock(&buffer->lock);
return 0;
}
static int cma_heap_dma_buf_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
flush_kernel_vmap_range(buffer->vaddr, buffer->len);
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_device(a->dev, &a->table, direction);
}
mutex_unlock(&buffer->lock);
return 0;
}
static vm_fault_t cma_heap_vm_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct cma_heap_buffer *buffer = vma->vm_private_data;
if (vmf->pgoff >= buffer->pagecount)
return VM_FAULT_SIGBUS;
return vmf_insert_pfn(vma, vmf->address, page_to_pfn(buffer->pages[vmf->pgoff]));
}
static const struct vm_operations_struct dma_heap_vm_ops = {
.fault = cma_heap_vm_fault,
};
static int cma_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0)
return -EINVAL;
vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
vma->vm_ops = &dma_heap_vm_ops;
vma->vm_private_data = buffer;
return 0;
}
static void *cma_heap_do_vmap(struct cma_heap_buffer *buffer)
{
void *vaddr;
vaddr = vmap(buffer->pages, buffer->pagecount, VM_MAP, PAGE_KERNEL);
if (!vaddr)
return ERR_PTR(-ENOMEM);
return vaddr;
}
static int cma_heap_vmap(struct dma_buf *dmabuf, struct iosys_map *map)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
void *vaddr;
int ret = 0;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt) {
buffer->vmap_cnt++;
iosys_map_set_vaddr(map, buffer->vaddr);
goto out;
}
vaddr = cma_heap_do_vmap(buffer);
if (IS_ERR(vaddr)) {
ret = PTR_ERR(vaddr);
goto out;
}
buffer->vaddr = vaddr;
buffer->vmap_cnt++;
iosys_map_set_vaddr(map, buffer->vaddr);
out:
mutex_unlock(&buffer->lock);
return ret;
}
static void cma_heap_vunmap(struct dma_buf *dmabuf, struct iosys_map *map)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
mutex_lock(&buffer->lock);
if (!--buffer->vmap_cnt) {
vunmap(buffer->vaddr);
buffer->vaddr = NULL;
}
mutex_unlock(&buffer->lock);
iosys_map_clear(map);
}
static void cma_heap_dma_buf_release(struct dma_buf *dmabuf)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct cma_heap *cma_heap = buffer->heap;
if (buffer->vmap_cnt > 0) {
WARN(1, "%s: buffer still mapped in the kernel\n", __func__);
vunmap(buffer->vaddr);
buffer->vaddr = NULL;
}
/* free page list */
kfree(buffer->pages);
/* release memory */
cma_release(cma_heap->cma, buffer->cma_pages, buffer->pagecount);
kfree(buffer);
}
static const struct dma_buf_ops cma_heap_buf_ops = {
.attach = cma_heap_attach,
.detach = cma_heap_detach,
.map_dma_buf = cma_heap_map_dma_buf,
.unmap_dma_buf = cma_heap_unmap_dma_buf,
.begin_cpu_access = cma_heap_dma_buf_begin_cpu_access,
.end_cpu_access = cma_heap_dma_buf_end_cpu_access,
.mmap = cma_heap_mmap,
.vmap = cma_heap_vmap,
.vunmap = cma_heap_vunmap,
.release = cma_heap_dma_buf_release,
};
static struct dma_buf *cma_heap_allocate(struct dma_heap *heap,
unsigned long len,
u32 fd_flags,
u64 heap_flags)
{
struct cma_heap *cma_heap = dma_heap_get_drvdata(heap);
struct cma_heap_buffer *buffer;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
size_t size = PAGE_ALIGN(len);
pgoff_t pagecount = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
struct page *cma_pages;
struct dma_buf *dmabuf;
int ret = -ENOMEM;
pgoff_t pg;
buffer = kzalloc_obj(*buffer);
if (!buffer)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&buffer->attachments);
mutex_init(&buffer->lock);
buffer->len = size;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
cma_pages = cma_alloc(cma_heap->cma, pagecount, align, false);
if (!cma_pages)
goto free_buffer;
/* Clear the cma pages */
if (PageHighMem(cma_pages)) {
unsigned long nr_clear_pages = pagecount;
struct page *page = cma_pages;
while (nr_clear_pages > 0) {
clear_highpage(page);
/*
* Avoid wasting time zeroing memory if the process
* has been killed by SIGKILL.
*/
if (fatal_signal_pending(current))
goto free_cma;
page++;
nr_clear_pages--;
}
} else {
clear_pages(page_address(cma_pages), pagecount);
}
buffer->pages = kmalloc_objs(*buffer->pages, pagecount);
if (!buffer->pages) {
ret = -ENOMEM;
goto free_cma;
}
for (pg = 0; pg < pagecount; pg++)
buffer->pages[pg] = &cma_pages[pg];
buffer->cma_pages = cma_pages;
buffer->heap = cma_heap;
buffer->pagecount = pagecount;
/* create the dmabuf */
exp_info.exp_name = dma_heap_get_name(heap);
exp_info.ops = &cma_heap_buf_ops;
exp_info.size = buffer->len;
exp_info.flags = fd_flags;
exp_info.priv = buffer;
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
ret = PTR_ERR(dmabuf);
goto free_pages;
}
return dmabuf;
free_pages:
kfree(buffer->pages);
free_cma:
cma_release(cma_heap->cma, cma_pages, pagecount);
free_buffer:
kfree(buffer);
return ERR_PTR(ret);
}
static const struct dma_heap_ops cma_heap_ops = {
.allocate = cma_heap_allocate,
};
static int __init __add_cma_heap(struct cma *cma, const char *name)
{
struct dma_heap_export_info exp_info;
struct cma_heap *cma_heap;
cma_heap = kzalloc_obj(*cma_heap);
if (!cma_heap)
return -ENOMEM;
cma_heap->cma = cma;
exp_info.name = name;
exp_info.ops = &cma_heap_ops;
exp_info.priv = cma_heap;
cma_heap->heap = dma_heap_add(&exp_info);
if (IS_ERR(cma_heap->heap)) {
int ret = PTR_ERR(cma_heap->heap);
kfree(cma_heap);
return ret;
}
return 0;
}
static int __init add_cma_heaps(void)
{
struct cma *default_cma = dev_get_cma_area(NULL);
struct cma *cma;
unsigned int i;
int ret;
if (default_cma) {
ret = __add_cma_heap(default_cma, DEFAULT_CMA_NAME);
if (ret)
return ret;
}
for (i = 0; (cma = dma_contiguous_get_area_by_idx(i)) != NULL; i++) {
ret = __add_cma_heap(cma, cma_get_name(cma));
if (ret) {
pr_warn("Failed to add CMA heap %s", cma_get_name(cma));
continue;
}
}
return 0;
}
module_init(add_cma_heaps);
MODULE_DESCRIPTION("DMA-BUF CMA Heap");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("DMA_BUF");
MODULE_IMPORT_NS("DMA_BUF_HEAP");
@@ -0,0 +1,548 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DMABUF System heap exporter
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2019, 2020 Linaro Ltd.
*
* Portions based off of Andrew Davis' SRAM heap:
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*/
#include <linux/cc_platform.h>
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <linux/dma-heap.h>
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/mem_encrypt.h>
#include <linux/mm.h>
#include <linux/set_memory.h>
#include <linux/module.h>
#include <linux/pgtable.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
struct system_heap_priv {
bool cc_shared;
};
struct system_heap_buffer {
struct dma_heap *heap;
struct list_head attachments;
struct mutex lock;
unsigned long len;
struct sg_table sg_table;
int vmap_cnt;
void *vaddr;
bool cc_shared;
};
struct dma_heap_attachment {
struct device *dev;
struct sg_table table;
struct list_head list;
bool mapped;
bool cc_shared;
};
#define cc_shared_buffer(b) (IS_ENABLED(CONFIG_DMABUF_HEAPS_SYSTEM_CC_SHARED) && \
(b)->cc_shared)
#define LOW_ORDER_GFP (GFP_HIGHUSER | __GFP_ZERO)
#define HIGH_ORDER_GFP (((GFP_HIGHUSER | __GFP_ZERO | __GFP_NOWARN \
| __GFP_NORETRY) & ~__GFP_RECLAIM) \
| __GFP_COMP)
static gfp_t order_flags[] = {HIGH_ORDER_GFP, HIGH_ORDER_GFP, LOW_ORDER_GFP};
/*
* The selection of the orders used for allocation (1MB, 64K, 4K) is designed
* to match with the sizes often found in IOMMUs. Using order 4 pages instead
* of order 0 pages can significantly improve the performance of many IOMMUs
* by reducing TLB pressure and time spent updating page tables.
*/
static const unsigned int orders[] = {8, 4, 0};
#define NUM_ORDERS ARRAY_SIZE(orders)
static int system_heap_set_page_decrypted(struct page *page)
{
unsigned long addr = (unsigned long)page_address(page);
unsigned int nr_pages = 1 << compound_order(page);
int ret;
ret = set_memory_decrypted(addr, nr_pages);
if (ret)
pr_warn_ratelimited("dma-buf system heap: failed to decrypt page at %p\n",
page_address(page));
return ret;
}
static int system_heap_set_page_encrypted(struct page *page)
{
unsigned long addr = (unsigned long)page_address(page);
unsigned int nr_pages = 1 << compound_order(page);
int ret;
ret = set_memory_encrypted(addr, nr_pages);
if (ret)
pr_warn_ratelimited("dma-buf system heap: failed to re-encrypt page at %p, leaking memory\n",
page_address(page));
return ret;
}
static int dup_sg_table(struct sg_table *from, struct sg_table *to)
{
struct scatterlist *sg, *new_sg;
int ret, i;
ret = sg_alloc_table(to, from->orig_nents, GFP_KERNEL);
if (ret)
return ret;
new_sg = to->sgl;
for_each_sgtable_sg(from, sg, i) {
sg_set_page(new_sg, sg_page(sg), sg->length, sg->offset);
new_sg = sg_next(new_sg);
}
return 0;
}
static int system_heap_attach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
int ret;
a = kzalloc_obj(*a);
if (!a)
return -ENOMEM;
ret = dup_sg_table(&buffer->sg_table, &a->table);
if (ret) {
kfree(a);
return ret;
}
a->dev = attachment->dev;
INIT_LIST_HEAD(&a->list);
a->mapped = false;
a->cc_shared = buffer->cc_shared;
attachment->priv = a;
mutex_lock(&buffer->lock);
list_add(&a->list, &buffer->attachments);
mutex_unlock(&buffer->lock);
return 0;
}
static void system_heap_detach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a = attachment->priv;
mutex_lock(&buffer->lock);
list_del(&a->list);
mutex_unlock(&buffer->lock);
sg_free_table(&a->table);
kfree(a);
}
static struct sg_table *system_heap_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
struct sg_table *table = &a->table;
unsigned long attrs;
int ret;
attrs = cc_shared_buffer(a) ? DMA_ATTR_CC_SHARED : 0;
ret = dma_map_sgtable(attachment->dev, table, direction, attrs);
if (ret)
return ERR_PTR(ret);
a->mapped = true;
return table;
}
static void system_heap_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
a->mapped = false;
dma_unmap_sgtable(attachment->dev, table, direction, 0);
}
static int system_heap_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
invalidate_kernel_vmap_range(buffer->vaddr, buffer->len);
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_cpu(a->dev, &a->table, direction);
}
mutex_unlock(&buffer->lock);
return 0;
}
static int system_heap_dma_buf_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
flush_kernel_vmap_range(buffer->vaddr, buffer->len);
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_device(a->dev, &a->table, direction);
}
mutex_unlock(&buffer->lock);
return 0;
}
static int system_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct sg_table *table = &buffer->sg_table;
unsigned long addr = vma->vm_start;
unsigned long pgoff = vma->vm_pgoff;
struct scatterlist *sg;
pgprot_t prot;
int i, ret;
prot = vma->vm_page_prot;
if (cc_shared_buffer(buffer))
prot = pgprot_decrypted(prot);
for_each_sgtable_sg(table, sg, i) {
unsigned long n = sg->length >> PAGE_SHIFT;
if (pgoff < n)
break;
pgoff -= n;
}
for (; sg && addr < vma->vm_end; sg = sg_next(sg)) {
unsigned long n = (sg->length >> PAGE_SHIFT) - pgoff;
struct page *page = sg_page(sg) + pgoff;
unsigned long size = n << PAGE_SHIFT;
if (addr + size > vma->vm_end)
size = vma->vm_end - addr;
ret = remap_pfn_range(vma, addr, page_to_pfn(page), size, prot);
if (ret)
return ret;
addr += size;
pgoff = 0;
}
return 0;
}
static void *system_heap_do_vmap(struct system_heap_buffer *buffer)
{
struct sg_table *table = &buffer->sg_table;
int npages = PAGE_ALIGN(buffer->len) / PAGE_SIZE;
struct page **pages = vmalloc(sizeof(struct page *) * npages);
struct page **tmp = pages;
struct sg_page_iter piter;
pgprot_t prot;
void *vaddr;
if (!pages)
return ERR_PTR(-ENOMEM);
for_each_sgtable_page(table, &piter, 0) {
WARN_ON(tmp - pages >= npages);
*tmp++ = sg_page_iter_page(&piter);
}
prot = PAGE_KERNEL;
if (cc_shared_buffer(buffer))
prot = pgprot_decrypted(prot);
vaddr = vmap(pages, npages, VM_MAP, prot);
vfree(pages);
if (!vaddr)
return ERR_PTR(-ENOMEM);
return vaddr;
}
static int system_heap_vmap(struct dma_buf *dmabuf, struct iosys_map *map)
{
struct system_heap_buffer *buffer = dmabuf->priv;
void *vaddr;
int ret = 0;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt) {
buffer->vmap_cnt++;
iosys_map_set_vaddr(map, buffer->vaddr);
goto out;
}
vaddr = system_heap_do_vmap(buffer);
if (IS_ERR(vaddr)) {
ret = PTR_ERR(vaddr);
goto out;
}
buffer->vaddr = vaddr;
buffer->vmap_cnt++;
iosys_map_set_vaddr(map, buffer->vaddr);
out:
mutex_unlock(&buffer->lock);
return ret;
}
static void system_heap_vunmap(struct dma_buf *dmabuf, struct iosys_map *map)
{
struct system_heap_buffer *buffer = dmabuf->priv;
mutex_lock(&buffer->lock);
if (!--buffer->vmap_cnt) {
vunmap(buffer->vaddr);
buffer->vaddr = NULL;
}
mutex_unlock(&buffer->lock);
iosys_map_clear(map);
}
static void system_heap_dma_buf_release(struct dma_buf *dmabuf)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct sg_table *table;
struct scatterlist *sg;
int i;
table = &buffer->sg_table;
for_each_sgtable_sg(table, sg, i) {
struct page *page = sg_page(sg);
/*
* Intentionally leak pages that cannot be re-encrypted
* to prevent shared memory from being reused.
*/
if (cc_shared_buffer(buffer) &&
system_heap_set_page_encrypted(page))
continue;
__free_pages(page, compound_order(page));
}
sg_free_table(table);
kfree(buffer);
}
static const struct dma_buf_ops system_heap_buf_ops = {
.attach = system_heap_attach,
.detach = system_heap_detach,
.map_dma_buf = system_heap_map_dma_buf,
.unmap_dma_buf = system_heap_unmap_dma_buf,
.begin_cpu_access = system_heap_dma_buf_begin_cpu_access,
.end_cpu_access = system_heap_dma_buf_end_cpu_access,
.mmap = system_heap_mmap,
.vmap = system_heap_vmap,
.vunmap = system_heap_vunmap,
.release = system_heap_dma_buf_release,
};
static struct page *alloc_largest_available(unsigned long size,
unsigned int max_order)
{
struct page *page;
int i;
gfp_t flags;
for (i = 0; i < NUM_ORDERS; i++) {
if (size < (PAGE_SIZE << orders[i]))
continue;
if (max_order < orders[i])
continue;
flags = order_flags[i];
if (mem_accounting)
flags |= __GFP_ACCOUNT;
page = alloc_pages(flags, orders[i]);
if (!page)
continue;
return page;
}
return NULL;
}
static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
unsigned long len,
u32 fd_flags,
u64 heap_flags)
{
struct system_heap_buffer *buffer;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
unsigned long size_remaining = len;
unsigned int max_order = orders[0];
struct system_heap_priv *priv = dma_heap_get_drvdata(heap);
bool cc_shared = priv->cc_shared;
struct dma_buf *dmabuf;
struct sg_table *table;
struct scatterlist *sg;
struct list_head pages;
struct page *page, *tmp_page;
int i, ret = -ENOMEM;
buffer = kzalloc_obj(*buffer);
if (!buffer)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&buffer->attachments);
mutex_init(&buffer->lock);
buffer->heap = heap;
buffer->len = len;
buffer->cc_shared = cc_shared;
INIT_LIST_HEAD(&pages);
i = 0;
while (size_remaining > 0) {
/*
* Avoid trying to allocate memory if the process
* has been killed by SIGKILL
*/
if (fatal_signal_pending(current)) {
ret = -EINTR;
goto free_buffer;
}
page = alloc_largest_available(size_remaining, max_order);
if (!page)
goto free_buffer;
list_add_tail(&page->lru, &pages);
size_remaining -= page_size(page);
max_order = compound_order(page);
i++;
}
table = &buffer->sg_table;
if (sg_alloc_table(table, i, GFP_KERNEL))
goto free_buffer;
sg = table->sgl;
list_for_each_entry_safe(page, tmp_page, &pages, lru) {
sg_set_page(sg, page, page_size(page), 0);
sg = sg_next(sg);
list_del(&page->lru);
}
if (cc_shared_buffer(buffer)) {
for_each_sgtable_sg(table, sg, i) {
ret = system_heap_set_page_decrypted(sg_page(sg));
if (ret)
goto free_pages;
}
}
/* create the dmabuf */
exp_info.exp_name = dma_heap_get_name(heap);
exp_info.ops = &system_heap_buf_ops;
exp_info.size = buffer->len;
exp_info.flags = fd_flags;
exp_info.priv = buffer;
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
ret = PTR_ERR(dmabuf);
goto free_pages;
}
return dmabuf;
free_pages:
for_each_sgtable_sg(table, sg, i) {
struct page *p = sg_page(sg);
/*
* Intentionally leak pages that cannot be re-encrypted
* to prevent shared memory from being reused.
*/
if (cc_shared_buffer(buffer) &&
system_heap_set_page_encrypted(p))
continue;
__free_pages(p, compound_order(p));
}
sg_free_table(table);
free_buffer:
list_for_each_entry_safe(page, tmp_page, &pages, lru)
__free_pages(page, compound_order(page));
kfree(buffer);
return ERR_PTR(ret);
}
static const struct dma_heap_ops system_heap_ops = {
.allocate = system_heap_allocate,
};
static struct system_heap_priv system_heap_priv = {
.cc_shared = false,
};
static struct system_heap_priv system_heap_cc_shared_priv = {
.cc_shared = true,
};
static int __init system_heap_create(void)
{
struct dma_heap_export_info exp_info;
struct dma_heap *sys_heap;
exp_info.name = "system";
exp_info.ops = &system_heap_ops;
exp_info.priv = &system_heap_priv;
sys_heap = dma_heap_add(&exp_info);
if (IS_ERR(sys_heap))
return PTR_ERR(sys_heap);
if (IS_ENABLED(CONFIG_HIGHMEM) ||
!IS_ENABLED(CONFIG_DMABUF_HEAPS_SYSTEM_CC_SHARED) ||
!cc_platform_has(CC_ATTR_MEM_ENCRYPT))
return 0;
exp_info.name = "system_cc_shared";
exp_info.priv = &system_heap_cc_shared_priv;
sys_heap = dma_heap_add(&exp_info);
if (IS_ERR(sys_heap))
return PTR_ERR(sys_heap);
return 0;
}
module_init(system_heap_create);
MODULE_DESCRIPTION("DMA-BUF System Heap");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("DMA_BUF");
MODULE_IMPORT_NS("DMA_BUF_HEAP");
@@ -0,0 +1,679 @@
// SPDX-License-Identifier: MIT
/*
* Copyright © 2019 Intel Corporation
*/
#include <kunit/test.h>
#include <linux/delay.h>
#include <linux/dma-fence.h>
#include <linux/dma-fence-chain.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/mm.h>
#include <linux/sched/signal.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/random.h>
#define CHAIN_SZ (4 << 10)
static struct kmem_cache *slab_fences;
static inline struct mock_fence {
struct dma_fence base;
spinlock_t lock;
} *to_mock_fence(struct dma_fence *f) {
return container_of(f, struct mock_fence, base);
}
static const char *mock_name(struct dma_fence *f)
{
return "mock";
}
static void mock_fence_release(struct dma_fence *f)
{
kmem_cache_free(slab_fences, to_mock_fence(f));
}
static const struct dma_fence_ops mock_ops = {
.get_driver_name = mock_name,
.get_timeline_name = mock_name,
.release = mock_fence_release,
};
static struct dma_fence *mock_fence(void)
{
struct mock_fence *f;
f = kmem_cache_alloc(slab_fences, GFP_KERNEL);
if (!f)
return NULL;
spin_lock_init(&f->lock);
dma_fence_init(&f->base, &mock_ops, &f->lock, 0, 0);
return &f->base;
}
static struct dma_fence *mock_chain(struct dma_fence *prev,
struct dma_fence *fence,
u64 seqno)
{
struct dma_fence_chain *f;
f = dma_fence_chain_alloc();
if (!f)
return NULL;
dma_fence_chain_init(f, dma_fence_get(prev), dma_fence_get(fence),
seqno);
return &f->base;
}
static void test_sanitycheck(struct kunit *test)
{
struct dma_fence *f, *chain;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
chain = mock_chain(NULL, f, 1);
if (chain)
dma_fence_enable_sw_signaling(chain);
else
KUNIT_FAIL(test, "Failed to create chain");
dma_fence_signal(f);
dma_fence_put(f);
dma_fence_put(chain);
}
struct fence_chains {
unsigned int chain_length;
struct dma_fence **fences;
struct dma_fence **chains;
struct dma_fence *tail;
};
static uint64_t seqno_inc(unsigned int i)
{
return i + 1;
}
static int fence_chains_init(struct fence_chains *fc, unsigned int count,
uint64_t (*seqno_fn)(unsigned int))
{
unsigned int i;
int err = 0;
fc->chains = kvmalloc_objs(*fc->chains, count, GFP_KERNEL | __GFP_ZERO);
if (!fc->chains)
return -ENOMEM;
fc->fences = kvmalloc_objs(*fc->fences, count, GFP_KERNEL | __GFP_ZERO);
if (!fc->fences) {
err = -ENOMEM;
goto err_chains;
}
fc->tail = NULL;
for (i = 0; i < count; i++) {
fc->fences[i] = mock_fence();
if (!fc->fences[i]) {
err = -ENOMEM;
goto unwind;
}
fc->chains[i] = mock_chain(fc->tail,
fc->fences[i],
seqno_fn(i));
if (!fc->chains[i]) {
err = -ENOMEM;
goto unwind;
}
fc->tail = fc->chains[i];
dma_fence_enable_sw_signaling(fc->chains[i]);
}
fc->chain_length = i;
return 0;
unwind:
for (i = 0; i < count; i++) {
dma_fence_put(fc->fences[i]);
dma_fence_put(fc->chains[i]);
}
kvfree(fc->fences);
err_chains:
kvfree(fc->chains);
return err;
}
static void fence_chains_fini(struct fence_chains *fc)
{
unsigned int i;
for (i = 0; i < fc->chain_length; i++) {
dma_fence_signal(fc->fences[i]);
dma_fence_put(fc->fences[i]);
}
kvfree(fc->fences);
for (i = 0; i < fc->chain_length; i++)
dma_fence_put(fc->chains[i]);
kvfree(fc->chains);
}
static void test_find_seqno(struct kunit *test)
{
struct fence_chains fc;
struct dma_fence *fence;
int err;
int i;
err = fence_chains_init(&fc, 64, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
fence = dma_fence_get(fc.tail);
err = dma_fence_chain_find_seqno(&fence, 0);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Reported %d for find_seqno(0)!", err);
goto err;
}
for (i = 0; i < fc.chain_length; i++) {
fence = dma_fence_get(fc.tail);
err = dma_fence_chain_find_seqno(&fence, i + 1);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Reported %d for find_seqno(%d:%d)!",
err, fc.chain_length + 1, i + 1);
goto err;
}
if (fence != fc.chains[i]) {
KUNIT_FAIL(test, "Incorrect fence reported by find_seqno(%d:%d)",
fc.chain_length + 1, i + 1);
goto err;
}
dma_fence_get(fence);
err = dma_fence_chain_find_seqno(&fence, i + 1);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Error reported for finding self");
goto err;
}
if (fence != fc.chains[i]) {
KUNIT_FAIL(test, "Incorrect fence reported by find self");
goto err;
}
dma_fence_get(fence);
err = dma_fence_chain_find_seqno(&fence, i + 2);
dma_fence_put(fence);
if (!err) {
KUNIT_FAIL(test, "Error not reported for future fence: find_seqno(%d:%d)!",
i + 1, i + 2);
goto err;
}
dma_fence_get(fence);
err = dma_fence_chain_find_seqno(&fence, i);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Error reported for previous fence!");
goto err;
}
if (i > 0 && fence != fc.chains[i - 1]) {
KUNIT_FAIL(test, "Incorrect fence reported by find_seqno(%d:%d)",
i + 1, i);
goto err;
}
}
err:
fence_chains_fini(&fc);
}
static void test_find_signaled(struct kunit *test)
{
struct fence_chains fc;
struct dma_fence *fence;
int err;
err = fence_chains_init(&fc, 2, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
dma_fence_signal(fc.fences[0]);
fence = dma_fence_get(fc.tail);
err = dma_fence_chain_find_seqno(&fence, 1);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Reported %d for find_seqno()!", err);
goto err;
}
if (fence && fence != fc.chains[0]) {
KUNIT_FAIL(test, "Incorrect chain-fence.seqno:%lld reported for completed seqno:1",
fence->seqno);
dma_fence_get(fence);
err = dma_fence_chain_find_seqno(&fence, 1);
dma_fence_put(fence);
if (err)
KUNIT_FAIL(test, "Reported %d for finding self!", err);
}
err:
fence_chains_fini(&fc);
}
static void test_find_out_of_order(struct kunit *test)
{
struct fence_chains fc;
struct dma_fence *fence;
int err;
err = fence_chains_init(&fc, 3, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
dma_fence_signal(fc.fences[1]);
fence = dma_fence_get(fc.tail);
err = dma_fence_chain_find_seqno(&fence, 2);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Reported %d for find_seqno()!", err);
goto err;
}
/*
* We signaled the middle fence (2) of the 1-2-3 chain. The behavior
* of the dma-fence-chain is to make us wait for all the fences up to
* the point we want. Since fence 1 is still not signaled, this what
* we should get as fence to wait upon (fence 2 being garbage
* collected during the traversal of the chain).
*/
if (fence != fc.chains[0])
KUNIT_FAIL(test, "Incorrect chain-fence.seqno:%lld reported for completed seqno:2",
fence ? fence->seqno : 0);
err:
fence_chains_fini(&fc);
}
static uint64_t seqno_inc2(unsigned int i)
{
return 2 * i + 2;
}
static void test_find_gap(struct kunit *test)
{
struct fence_chains fc;
struct dma_fence *fence;
int err;
int i;
err = fence_chains_init(&fc, 64, seqno_inc2);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
for (i = 0; i < fc.chain_length; i++) {
fence = dma_fence_get(fc.tail);
err = dma_fence_chain_find_seqno(&fence, 2 * i + 1);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Reported %d for find_seqno(%d:%d)!",
err, fc.chain_length + 1, 2 * i + 1);
goto err;
}
if (fence != fc.chains[i]) {
KUNIT_FAIL(test, "Incorrect fence.seqno:%lld reported by find_seqno(%d:%d)",
fence->seqno,
fc.chain_length + 1,
2 * i + 1);
goto err;
}
dma_fence_get(fence);
err = dma_fence_chain_find_seqno(&fence, 2 * i + 2);
dma_fence_put(fence);
if (err) {
KUNIT_FAIL(test, "Error reported for finding self");
goto err;
}
if (fence != fc.chains[i]) {
KUNIT_FAIL(test, "Incorrect fence reported by find self");
goto err;
}
}
err:
fence_chains_fini(&fc);
}
struct find_race {
struct fence_chains fc;
atomic_t children;
};
static int __find_race(void *arg)
{
struct find_race *data = arg;
int err = 0;
while (!kthread_should_stop()) {
struct dma_fence *fence = dma_fence_get(data->fc.tail);
int seqno;
seqno = get_random_u32_inclusive(1, data->fc.chain_length);
err = dma_fence_chain_find_seqno(&fence, seqno);
if (err) {
pr_err("Failed to find fence seqno:%d\n",
seqno);
dma_fence_put(fence);
break;
}
if (!fence)
goto signal;
/*
* We can only find ourselves if we are on fence we were
* looking for.
*/
if (fence->seqno == seqno) {
err = dma_fence_chain_find_seqno(&fence, seqno);
if (err) {
pr_err("Reported an invalid fence for find-self:%d\n",
seqno);
dma_fence_put(fence);
break;
}
}
dma_fence_put(fence);
signal:
seqno = get_random_u32_below(data->fc.chain_length - 1);
dma_fence_signal(data->fc.fences[seqno]);
cond_resched();
}
if (atomic_dec_and_test(&data->children))
wake_up_var(&data->children);
return err;
}
static void test_find_race(struct kunit *test)
{
struct find_race data;
int ncpus = num_online_cpus();
struct task_struct **threads;
unsigned long count;
int err;
int i;
err = fence_chains_init(&data.fc, CHAIN_SZ, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
threads = kmalloc_objs(*threads, ncpus);
if (!threads) {
KUNIT_FAIL(test, "Failed to allocate threads array");
goto err;
}
atomic_set(&data.children, 0);
for (i = 0; i < ncpus; i++) {
threads[i] = kthread_run(__find_race, &data, "dmabuf/%d", i);
if (IS_ERR(threads[i])) {
ncpus = i;
break;
}
atomic_inc(&data.children);
get_task_struct(threads[i]);
}
wait_var_event_timeout(&data.children,
!atomic_read(&data.children),
5 * HZ);
for (i = 0; i < ncpus; i++) {
int ret;
ret = kthread_stop_put(threads[i]);
if (ret && !err)
err = ret;
}
kfree(threads);
count = 0;
for (i = 0; i < data.fc.chain_length; i++)
if (dma_fence_is_signaled(data.fc.fences[i]))
count++;
pr_info("Completed %lu cycles\n", count);
KUNIT_EXPECT_EQ(test, err, 0);
err:
fence_chains_fini(&data.fc);
}
static void test_signal_forward(struct kunit *test)
{
struct fence_chains fc;
int err;
int i;
err = fence_chains_init(&fc, 64, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
for (i = 0; i < fc.chain_length; i++) {
dma_fence_signal(fc.fences[i]);
if (!dma_fence_is_signaled(fc.chains[i])) {
KUNIT_FAIL(test, "chain[%d] not signaled!", i);
goto err;
}
if (i + 1 < fc.chain_length &&
dma_fence_is_signaled(fc.chains[i + 1])) {
KUNIT_FAIL(test, "chain[%d] is signaled!", i);
goto err;
}
}
err:
fence_chains_fini(&fc);
}
static void test_signal_backward(struct kunit *test)
{
struct fence_chains fc;
int err;
int i;
err = fence_chains_init(&fc, 64, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
for (i = fc.chain_length; i--; ) {
dma_fence_signal(fc.fences[i]);
if (i > 0 && dma_fence_is_signaled(fc.chains[i])) {
KUNIT_FAIL(test, "chain[%d] is signaled!", i);
goto err;
}
}
for (i = 0; i < fc.chain_length; i++) {
if (!dma_fence_is_signaled(fc.chains[i])) {
KUNIT_FAIL(test, "chain[%d] was not signaled!", i);
goto err;
}
}
err:
fence_chains_fini(&fc);
}
static int __wait_fence_chains(void *arg)
{
struct fence_chains *fc = arg;
if (dma_fence_wait(fc->tail, false))
return -EIO;
return 0;
}
static void test_wait_forward(struct kunit *test)
{
struct fence_chains fc;
struct task_struct *tsk;
int err;
int i;
err = fence_chains_init(&fc, CHAIN_SZ, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
if (IS_ERR(tsk)) {
KUNIT_FAIL(test, "Failed to create kthread");
goto err;
}
get_task_struct(tsk);
yield_to(tsk, true);
for (i = 0; i < fc.chain_length; i++)
dma_fence_signal(fc.fences[i]);
err = kthread_stop_put(tsk);
KUNIT_EXPECT_EQ(test, err, 0);
err:
fence_chains_fini(&fc);
}
static void test_wait_backward(struct kunit *test)
{
struct fence_chains fc;
struct task_struct *tsk;
int err;
int i;
err = fence_chains_init(&fc, CHAIN_SZ, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
if (IS_ERR(tsk)) {
KUNIT_FAIL(test, "Failed to create kthread");
goto err;
}
get_task_struct(tsk);
yield_to(tsk, true);
for (i = fc.chain_length; i--; )
dma_fence_signal(fc.fences[i]);
err = kthread_stop_put(tsk);
KUNIT_EXPECT_EQ(test, err, 0);
err:
fence_chains_fini(&fc);
}
static void randomise_fences(struct fence_chains *fc)
{
unsigned int count = fc->chain_length;
/* Fisher-Yates shuffle courtesy of Knuth */
while (--count) {
unsigned int swp;
swp = get_random_u32_below(count + 1);
if (swp == count)
continue;
swap(fc->fences[count], fc->fences[swp]);
}
}
static void test_wait_random(struct kunit *test)
{
struct fence_chains fc;
struct task_struct *tsk;
int err;
int i;
err = fence_chains_init(&fc, CHAIN_SZ, seqno_inc);
KUNIT_ASSERT_EQ_MSG(test, err, 0, "Failed to init fence chains");
randomise_fences(&fc);
tsk = kthread_run(__wait_fence_chains, &fc, "dmabuf/wait");
if (IS_ERR(tsk)) {
KUNIT_FAIL(test, "Failed to create kthread");
goto err;
}
get_task_struct(tsk);
yield_to(tsk, true);
for (i = 0; i < fc.chain_length; i++)
dma_fence_signal(fc.fences[i]);
err = kthread_stop_put(tsk);
KUNIT_EXPECT_EQ(test, err, 0);
err:
fence_chains_fini(&fc);
}
static int dma_fence_chain_suite_init(struct kunit_suite *suite)
{
pr_info("sizeof(dma_fence_chain)=%zu\n",
sizeof(struct dma_fence_chain));
slab_fences = KMEM_CACHE(mock_fence,
SLAB_TYPESAFE_BY_RCU |
SLAB_HWCACHE_ALIGN);
if (!slab_fences)
return -ENOMEM;
return 0;
}
static void dma_fence_chain_suite_exit(struct kunit_suite *suite)
{
kmem_cache_destroy(slab_fences);
}
static struct kunit_case dma_fence_chain_cases[] = {
KUNIT_CASE(test_sanitycheck),
KUNIT_CASE(test_find_seqno),
KUNIT_CASE(test_find_signaled),
KUNIT_CASE(test_find_out_of_order),
KUNIT_CASE(test_find_gap),
KUNIT_CASE(test_find_race),
KUNIT_CASE(test_signal_forward),
KUNIT_CASE(test_signal_backward),
KUNIT_CASE(test_wait_forward),
KUNIT_CASE(test_wait_backward),
KUNIT_CASE(test_wait_random),
{}
};
static struct kunit_suite dma_fence_chain_test_suite = {
.name = "dma-buf-fence-chain",
.suite_init = dma_fence_chain_suite_init,
.suite_exit = dma_fence_chain_suite_exit,
.test_cases = dma_fence_chain_cases,
};
kunit_test_suite(dma_fence_chain_test_suite);
@@ -0,0 +1,606 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2022 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include <linux/dma-fence.h>
#include <linux/dma-fence-array.h>
#include <linux/dma-fence-chain.h>
#include <linux/dma-fence-unwrap.h>
#define CHAIN_SZ (4 << 10)
struct mock_fence {
struct dma_fence base;
spinlock_t lock;
};
static const char *mock_name(struct dma_fence *f)
{
return "mock";
}
static const struct dma_fence_ops mock_ops = {
.get_driver_name = mock_name,
.get_timeline_name = mock_name,
};
static struct dma_fence *__mock_fence(u64 context, u64 seqno)
{
struct mock_fence *f;
f = kmalloc_obj(*f);
if (!f)
return NULL;
spin_lock_init(&f->lock);
dma_fence_init(&f->base, &mock_ops, &f->lock, context, seqno);
return &f->base;
}
static struct dma_fence *mock_fence(void)
{
return __mock_fence(dma_fence_context_alloc(1), 1);
}
static struct dma_fence *mock_array(unsigned int num_fences, ...)
{
struct dma_fence_array *array;
struct dma_fence **fences;
va_list valist;
int i;
fences = kzalloc_objs(*fences, num_fences);
if (!fences)
goto error_put;
va_start(valist, num_fences);
for (i = 0; i < num_fences; ++i)
fences[i] = va_arg(valist, typeof(*fences));
va_end(valist);
array = dma_fence_array_create(num_fences, fences,
dma_fence_context_alloc(1),
1);
if (!array)
goto error_free;
return &array->base;
error_free:
kfree(fences);
error_put:
va_start(valist, num_fences);
for (i = 0; i < num_fences; ++i)
dma_fence_put(va_arg(valist, typeof(*fences)));
va_end(valist);
return NULL;
}
static struct dma_fence *mock_chain(struct dma_fence *prev,
struct dma_fence *fence)
{
struct dma_fence_chain *f;
f = dma_fence_chain_alloc();
if (!f) {
dma_fence_put(prev);
dma_fence_put(fence);
return NULL;
}
dma_fence_chain_init(f, prev, fence, 1);
return &f->base;
}
static void test_sanitycheck(struct kunit *test)
{
struct dma_fence *f, *chain, *array;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
array = mock_array(1, f);
KUNIT_ASSERT_NOT_NULL(test, array);
chain = mock_chain(NULL, array);
KUNIT_ASSERT_NOT_NULL(test, chain);
dma_fence_put(chain);
}
static void test_unwrap_array(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *array;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = mock_fence();
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
dma_fence_put(f1);
return;
}
dma_fence_enable_sw_signaling(f2);
array = mock_array(2, f1, f2);
KUNIT_ASSERT_NOT_NULL(test, array);
dma_fence_unwrap_for_each(fence, &iter, array) {
if (fence == f1) {
f1 = NULL;
} else if (fence == f2) {
f2 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f2)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(array);
}
static void test_unwrap_chain(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *chain;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = mock_fence();
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
dma_fence_put(f1);
return;
}
dma_fence_enable_sw_signaling(f2);
chain = mock_chain(f1, f2);
KUNIT_ASSERT_NOT_NULL(test, chain);
dma_fence_unwrap_for_each(fence, &iter, chain) {
if (fence == f1) {
f1 = NULL;
} else if (fence == f2) {
f2 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f2)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(chain);
}
static void test_unwrap_chain_array(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *array, *chain;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = mock_fence();
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
dma_fence_put(f1);
return;
}
dma_fence_enable_sw_signaling(f2);
array = mock_array(2, f1, f2);
KUNIT_ASSERT_NOT_NULL(test, array);
chain = mock_chain(NULL, array);
KUNIT_ASSERT_NOT_NULL(test, chain);
dma_fence_unwrap_for_each(fence, &iter, chain) {
if (fence == f1) {
f1 = NULL;
} else if (fence == f2) {
f2 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f2)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(chain);
}
static void test_unwrap_merge(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *f3;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = mock_fence();
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f1;
}
dma_fence_enable_sw_signaling(f2);
f3 = dma_fence_unwrap_merge(f1, f2);
if (!f3) {
KUNIT_FAIL(test, "Failed to merge fences");
goto error_put_f2;
}
dma_fence_unwrap_for_each(fence, &iter, f3) {
if (fence == f1) {
dma_fence_put(f1);
f1 = NULL;
} else if (fence == f2) {
dma_fence_put(f2);
f2 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f2)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(f3);
error_put_f2:
dma_fence_put(f2);
error_put_f1:
dma_fence_put(f1);
}
static void test_unwrap_merge_duplicate(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = dma_fence_unwrap_merge(f1, f1);
if (!f2) {
KUNIT_FAIL(test, "Failed to merge fences");
goto error_put_f1;
}
dma_fence_unwrap_for_each(fence, &iter, f2) {
if (fence == f1) {
dma_fence_put(f1);
f1 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(f2);
error_put_f1:
dma_fence_put(f1);
}
static void test_unwrap_merge_seqno(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *f3, *f4;
struct dma_fence_unwrap iter;
u64 ctx[2];
ctx[0] = dma_fence_context_alloc(1);
ctx[1] = dma_fence_context_alloc(1);
f1 = __mock_fence(ctx[1], 1);
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = __mock_fence(ctx[1], 2);
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f1;
}
dma_fence_enable_sw_signaling(f2);
f3 = __mock_fence(ctx[0], 1);
if (!f3) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f2;
}
dma_fence_enable_sw_signaling(f3);
f4 = dma_fence_unwrap_merge(f1, f2, f3);
if (!f4) {
KUNIT_FAIL(test, "Failed to merge fences");
goto error_put_f3;
}
dma_fence_unwrap_for_each(fence, &iter, f4) {
if (fence == f3 && f2) {
dma_fence_put(f3);
f3 = NULL;
} else if (fence == f2 && !f3) {
dma_fence_put(f2);
f2 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f2 || f3)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(f4);
error_put_f3:
dma_fence_put(f3);
error_put_f2:
dma_fence_put(f2);
error_put_f1:
dma_fence_put(f1);
}
static void test_unwrap_merge_order(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *a1, *a2, *c1, *c2;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = mock_fence();
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
dma_fence_put(f1);
return;
}
dma_fence_enable_sw_signaling(f2);
a1 = mock_array(2, f1, f2);
KUNIT_ASSERT_NOT_NULL(test, a1);
c1 = mock_chain(NULL, dma_fence_get(f1));
if (!c1) {
KUNIT_FAIL(test, "Failed to create chain");
goto error_put_a1;
}
c2 = mock_chain(c1, dma_fence_get(f2));
if (!c2) {
KUNIT_FAIL(test, "Failed to create chain");
goto error_put_a1;
}
/*
* The fences in the chain are the same as in a1 but in oposite order,
* the dma_fence_merge() function should be able to handle that.
*/
a2 = dma_fence_unwrap_merge(a1, c2);
dma_fence_unwrap_for_each(fence, &iter, a2) {
if (fence == f1) {
f1 = NULL;
if (!f2)
KUNIT_FAIL(test, "Unexpected order!");
} else if (fence == f2) {
f2 = NULL;
if (f1)
KUNIT_FAIL(test, "Unexpected order!");
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f2)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(a2);
return;
error_put_a1:
dma_fence_put(a1);
}
static void test_unwrap_merge_complex(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *f3, *f4, *f5;
struct dma_fence_unwrap iter;
f1 = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = mock_fence();
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f1;
}
dma_fence_enable_sw_signaling(f2);
f3 = dma_fence_unwrap_merge(f1, f2);
if (!f3) {
KUNIT_FAIL(test, "Failed to merge fences");
goto error_put_f2;
}
/* The resulting array has the fences in reverse */
f4 = mock_array(2, dma_fence_get(f2), dma_fence_get(f1));
if (!f4) {
KUNIT_FAIL(test, "Failed to create array");
goto error_put_f3;
}
/* Signaled fences should be filtered, the two arrays merged. */
f5 = dma_fence_unwrap_merge(f3, f4, dma_fence_get_stub());
if (!f5) {
KUNIT_FAIL(test, "Failed to merge fences");
goto error_put_f4;
}
dma_fence_unwrap_for_each(fence, &iter, f5) {
if (fence == f1) {
dma_fence_put(f1);
f1 = NULL;
} else if (fence == f2) {
dma_fence_put(f2);
f2 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f2)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(f5);
error_put_f4:
dma_fence_put(f4);
error_put_f3:
dma_fence_put(f3);
error_put_f2:
dma_fence_put(f2);
error_put_f1:
dma_fence_put(f1);
}
static void test_unwrap_merge_complex_seqno(struct kunit *test)
{
struct dma_fence *fence, *f1, *f2, *f3, *f4, *f5, *f6, *f7;
struct dma_fence_unwrap iter;
u64 ctx[2];
ctx[0] = dma_fence_context_alloc(1);
ctx[1] = dma_fence_context_alloc(1);
f1 = __mock_fence(ctx[0], 2);
KUNIT_ASSERT_NOT_NULL(test, f1);
dma_fence_enable_sw_signaling(f1);
f2 = __mock_fence(ctx[1], 1);
if (!f2) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f1;
}
dma_fence_enable_sw_signaling(f2);
f3 = __mock_fence(ctx[0], 1);
if (!f3) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f2;
}
dma_fence_enable_sw_signaling(f3);
f4 = __mock_fence(ctx[1], 2);
if (!f4) {
KUNIT_FAIL(test, "Failed to create mock fence");
goto error_put_f3;
}
dma_fence_enable_sw_signaling(f4);
f5 = mock_array(2, dma_fence_get(f1), dma_fence_get(f2));
if (!f5) {
KUNIT_FAIL(test, "Failed to create array");
goto error_put_f4;
}
f6 = mock_array(2, dma_fence_get(f3), dma_fence_get(f4));
if (!f6) {
KUNIT_FAIL(test, "Failed to create array");
goto error_put_f5;
}
f7 = dma_fence_unwrap_merge(f5, f6);
if (!f7) {
KUNIT_FAIL(test, "Failed to merge fences");
goto error_put_f6;
}
dma_fence_unwrap_for_each(fence, &iter, f7) {
if (fence == f1 && f4) {
dma_fence_put(f1);
f1 = NULL;
} else if (fence == f4 && !f1) {
dma_fence_put(f4);
f4 = NULL;
} else {
KUNIT_FAIL(test, "Unexpected fence!");
}
}
if (f1 || f4)
KUNIT_FAIL(test, "Not all fences seen!");
dma_fence_put(f7);
error_put_f6:
dma_fence_put(f6);
error_put_f5:
dma_fence_put(f5);
error_put_f4:
dma_fence_put(f4);
error_put_f3:
dma_fence_put(f3);
error_put_f2:
dma_fence_put(f2);
error_put_f1:
dma_fence_put(f1);
}
static struct kunit_case dma_fence_unwrap_cases[] = {
KUNIT_CASE(test_sanitycheck),
KUNIT_CASE(test_unwrap_array),
KUNIT_CASE(test_unwrap_chain),
KUNIT_CASE(test_unwrap_chain_array),
KUNIT_CASE(test_unwrap_merge),
KUNIT_CASE(test_unwrap_merge_duplicate),
KUNIT_CASE(test_unwrap_merge_seqno),
KUNIT_CASE(test_unwrap_merge_order),
KUNIT_CASE(test_unwrap_merge_complex),
KUNIT_CASE(test_unwrap_merge_complex_seqno),
{}
};
static struct kunit_suite dma_fence_unwrap_test_suite = {
.name = "dma-buf-fence-unwrap",
.test_cases = dma_fence_unwrap_cases,
};
kunit_test_suite(dma_fence_unwrap_test_suite);
+508
View File
@@ -0,0 +1,508 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2019 Intel Corporation
*/
#include <kunit/test.h>
#include <linux/delay.h>
#include <linux/dma-fence.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched/signal.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
static const char *mock_name(struct dma_fence *f)
{
return "mock";
}
static const struct dma_fence_ops mock_ops = {
.get_driver_name = mock_name,
.get_timeline_name = mock_name,
};
static struct dma_fence *mock_fence(void)
{
struct dma_fence *f;
f = kmalloc(sizeof(*f), GFP_KERNEL);
if (!f)
return NULL;
dma_fence_init(f, &mock_ops, NULL, 0, 0);
return f;
}
static void test_sanitycheck(struct kunit *test)
{
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_fence_signal(f);
dma_fence_put(f);
}
static void test_signaling(struct kunit *test)
{
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
if (dma_fence_is_signaled(f)) {
KUNIT_FAIL(test, "Fence unexpectedly signaled on creation");
goto err_free;
}
if (dma_fence_check_and_signal(f)) {
KUNIT_FAIL(test, "Fence reported being already signaled");
goto err_free;
}
if (!dma_fence_is_signaled(f)) {
KUNIT_FAIL(test, "Fence not reporting signaled");
goto err_free;
}
if (!dma_fence_test_signaled_flag(f)) {
KUNIT_FAIL(test, "Fence reported not being already signaled");
goto err_free;
}
if (rcu_dereference_protected(f->ops, true)) {
KUNIT_FAIL(test, "Fence ops not cleared on signal");
goto err_free;
}
err_free:
dma_fence_put(f);
}
struct simple_cb {
struct dma_fence_cb cb;
bool seen;
};
static void simple_callback(struct dma_fence *f, struct dma_fence_cb *cb)
{
smp_store_mb(container_of(cb, struct simple_cb, cb)->seen, true);
}
static void test_add_callback(struct kunit *test)
{
struct simple_cb cb = {};
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
if (dma_fence_add_callback(f, &cb.cb, simple_callback)) {
KUNIT_FAIL(test, "Failed to add callback, fence already signaled!");
goto err_free;
}
dma_fence_signal(f);
if (!cb.seen) {
KUNIT_FAIL(test, "Callback failed!");
goto err_free;
}
err_free:
dma_fence_put(f);
}
static void test_late_add_callback(struct kunit *test)
{
struct simple_cb cb = {};
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_fence_signal(f);
if (!dma_fence_add_callback(f, &cb.cb, simple_callback)) {
KUNIT_FAIL(test, "Added callback, but fence was already signaled!");
goto err_free;
}
dma_fence_signal(f);
if (cb.seen) {
KUNIT_FAIL(test, "Callback called after failed attachment!");
goto err_free;
}
err_free:
dma_fence_put(f);
}
static void test_rm_callback(struct kunit *test)
{
struct simple_cb cb = {};
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
if (dma_fence_add_callback(f, &cb.cb, simple_callback)) {
KUNIT_FAIL(test, "Failed to add callback, fence already signaled!");
goto err_free;
}
if (!dma_fence_remove_callback(f, &cb.cb)) {
KUNIT_FAIL(test, "Failed to remove callback!");
goto err_free;
}
dma_fence_signal(f);
if (cb.seen) {
KUNIT_FAIL(test, "Callback still signaled after removal!");
goto err_free;
}
err_free:
dma_fence_put(f);
}
static void test_late_rm_callback(struct kunit *test)
{
struct simple_cb cb = {};
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
if (dma_fence_add_callback(f, &cb.cb, simple_callback)) {
KUNIT_FAIL(test, "Failed to add callback, fence already signaled!");
goto err_free;
}
dma_fence_signal(f);
if (!cb.seen) {
KUNIT_FAIL(test, "Callback failed!");
goto err_free;
}
if (dma_fence_remove_callback(f, &cb.cb)) {
KUNIT_FAIL(test, "Callback removal succeeded after being executed!");
goto err_free;
}
err_free:
dma_fence_put(f);
}
static void test_status(struct kunit *test)
{
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
if (dma_fence_get_status(f)) {
KUNIT_FAIL(test, "Fence unexpectedly has signaled status on creation");
goto err_free;
}
dma_fence_signal(f);
if (!dma_fence_get_status(f)) {
KUNIT_FAIL(test, "Fence not reporting signaled status");
goto err_free;
}
err_free:
dma_fence_put(f);
}
static void test_error(struct kunit *test)
{
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_fence_set_error(f, -EIO);
if (dma_fence_get_status(f)) {
KUNIT_FAIL(test, "Fence unexpectedly has error status before signal");
goto err_free;
}
dma_fence_signal(f);
if (dma_fence_get_status(f) != -EIO) {
KUNIT_FAIL(test, "Fence not reporting error status, got %d",
dma_fence_get_status(f));
goto err_free;
}
err_free:
dma_fence_put(f);
}
static void test_wait(struct kunit *test)
{
struct dma_fence *f;
f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
if (dma_fence_wait_timeout(f, false, 0) != 0) {
KUNIT_FAIL(test, "Wait reported complete before being signaled");
goto err_free;
}
dma_fence_signal(f);
if (dma_fence_wait_timeout(f, false, 0) != 1) {
KUNIT_FAIL(test, "Wait reported incomplete after being signaled");
goto err_free;
}
err_free:
dma_fence_signal(f);
dma_fence_put(f);
}
struct wait_timer {
struct timer_list timer;
struct dma_fence *f;
};
static void wait_timer(struct timer_list *timer)
{
struct wait_timer *wt = timer_container_of(wt, timer, timer);
dma_fence_signal(wt->f);
}
static void test_wait_timeout(struct kunit *test)
{
struct wait_timer wt;
timer_setup_on_stack(&wt.timer, wait_timer, 0);
wt.f = mock_fence();
KUNIT_ASSERT_NOT_NULL(test, wt.f);
dma_fence_enable_sw_signaling(wt.f);
if (dma_fence_wait_timeout(wt.f, false, 1) != 0) {
KUNIT_FAIL(test, "Wait reported complete before being signaled");
goto err_free;
}
mod_timer(&wt.timer, jiffies + 1);
if (dma_fence_wait_timeout(wt.f, false, HZ) == 0) {
if (timer_pending(&wt.timer)) {
kunit_mark_skipped(
test, "Timer did not fire within on HZ!\n");
} else {
KUNIT_FAIL(test,
"Wait reported incomplete after timeout");
}
goto err_free;
}
err_free:
timer_delete_sync(&wt.timer);
timer_destroy_on_stack(&wt.timer);
dma_fence_signal(wt.f);
dma_fence_put(wt.f);
}
static void test_stub(struct kunit *test)
{
struct dma_fence *f[64];
int i;
for (i = 0; i < ARRAY_SIZE(f); i++) {
f[i] = dma_fence_get_stub();
if (!dma_fence_is_signaled(f[i])) {
KUNIT_FAIL(test, "Obtained unsignaled stub fence!");
goto err;
}
}
err:
while (i--)
dma_fence_put(f[i]);
}
/* Now off to the races! */
struct race_thread {
struct dma_fence __rcu **fences;
struct task_struct *task;
bool before;
int id;
};
static void __wait_for_callbacks(struct dma_fence *f)
{
unsigned long flags;
dma_fence_lock_irqsave(f, flags);
dma_fence_unlock_irqrestore(f, flags);
}
static int thread_signal_callback(void *arg)
{
const struct race_thread *t = arg;
unsigned long pass = 0;
unsigned long miss = 0;
int err = 0;
while (!err && !kthread_should_stop()) {
struct dma_fence *f1, *f2;
struct simple_cb cb;
f1 = mock_fence();
if (!f1) {
err = -ENOMEM;
break;
}
dma_fence_enable_sw_signaling(f1);
rcu_assign_pointer(t->fences[t->id], f1);
smp_wmb();
rcu_read_lock();
do {
f2 = dma_fence_get_rcu_safe(&t->fences[!t->id]);
} while (!f2 && !kthread_should_stop());
rcu_read_unlock();
if (t->before)
dma_fence_signal(f1);
smp_store_mb(cb.seen, false);
if (!f2 ||
dma_fence_add_callback(f2, &cb.cb, simple_callback)) {
miss++;
cb.seen = true;
}
if (!t->before)
dma_fence_signal(f1);
if (!cb.seen) {
dma_fence_wait(f2, false);
__wait_for_callbacks(f2);
}
if (!READ_ONCE(cb.seen)) {
pr_err("Callback not seen on thread %d, pass %lu (%lu misses), signaling %s add_callback; fence signaled? %s\n",
t->id, pass, miss,
t->before ? "before" : "after",
dma_fence_is_signaled(f2) ? "yes" : "no");
err = -EINVAL;
}
dma_fence_put(f2);
rcu_assign_pointer(t->fences[t->id], NULL);
smp_wmb();
dma_fence_put(f1);
pass++;
}
pr_info("%s[%d] completed %lu passes, %lu misses\n",
__func__, t->id, pass, miss);
return err;
}
static void test_race_signal_callback(struct kunit *test)
{
struct dma_fence __rcu *f[2] = {};
int ret = 0;
int pass;
/*
* thread_signal_callback() spins under RCU and it cannot make forward
* progress unless the threads are truly running concurrently.
*/
if (num_online_cpus() < 2)
kunit_skip(test, "requires at least 2 CPUs");
for (pass = 0; !ret && pass <= 1; pass++) {
struct race_thread t[2];
int i;
for (i = 0; i < ARRAY_SIZE(t); i++) {
t[i].fences = f;
t[i].id = i;
t[i].before = pass;
t[i].task = kthread_run(thread_signal_callback, &t[i],
"dma-fence:%d", i);
if (IS_ERR(t[i].task)) {
KUNIT_FAIL(test, "Failed to create kthread");
while (--i >= 0)
kthread_stop_put(t[i].task);
return;
}
get_task_struct(t[i].task);
}
msleep(50);
for (i = 0; i < ARRAY_SIZE(t); i++) {
int err;
err = kthread_stop_put(t[i].task);
if (err && !ret)
ret = err;
}
}
KUNIT_EXPECT_EQ(test, ret, 0);
}
static int dma_fence_suite_init(struct kunit_suite *suite)
{
pr_info("sizeof(dma_fence)=%zu\n", sizeof(struct dma_fence));
return 0;
}
static struct kunit_case dma_fence_cases[] = {
KUNIT_CASE(test_sanitycheck),
KUNIT_CASE(test_signaling),
KUNIT_CASE(test_add_callback),
KUNIT_CASE(test_late_add_callback),
KUNIT_CASE(test_rm_callback),
KUNIT_CASE(test_late_rm_callback),
KUNIT_CASE(test_status),
KUNIT_CASE(test_error),
KUNIT_CASE(test_wait),
KUNIT_CASE(test_wait_timeout),
KUNIT_CASE(test_stub),
KUNIT_CASE(test_race_signal_callback),
{}
};
static struct kunit_suite dma_fence_test_suite = {
.name = "dma-buf-fence",
.suite_init = dma_fence_suite_init,
.test_cases = dma_fence_cases,
};
kunit_test_suite(dma_fence_test_suite);
+315
View File
@@ -0,0 +1,315 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2019 Intel Corporation
* Copyright © 2021 Advanced Micro Devices, Inc.
*/
#include <kunit/test.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/dma-resv.h>
static DEFINE_SPINLOCK(fence_lock);
struct dma_resv_usage_param {
enum dma_resv_usage usage;
const char *desc;
};
static const char *fence_name(struct dma_fence *f)
{
return "selftest";
}
static const struct dma_fence_ops fence_ops = {
.get_driver_name = fence_name,
.get_timeline_name = fence_name,
};
static struct dma_fence *alloc_fence(void)
{
struct dma_fence *f;
f = kmalloc_obj(*f);
if (!f)
return NULL;
dma_fence_init(f, &fence_ops, &fence_lock, 0, 0);
return f;
}
static void test_sanitycheck(struct kunit *test)
{
struct dma_resv resv;
struct dma_fence *f;
int r;
f = alloc_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_fence_signal(f);
dma_fence_put(f);
dma_resv_init(&resv);
r = dma_resv_lock(&resv, NULL);
if (r)
KUNIT_FAIL(test, "Resv locking failed\n");
else
dma_resv_unlock(&resv);
dma_resv_fini(&resv);
}
static void test_signaling(struct kunit *test)
{
const struct dma_resv_usage_param *param = test->param_value;
enum dma_resv_usage usage = param->usage;
struct dma_resv resv;
struct dma_fence *f;
int r;
f = alloc_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_resv_init(&resv);
r = dma_resv_lock(&resv, NULL);
if (r) {
KUNIT_FAIL(test, "Resv locking failed");
goto err_free;
}
r = dma_resv_reserve_fences(&resv, 1);
if (r) {
KUNIT_FAIL(test, "Resv shared slot allocation failed");
goto err_unlock;
}
dma_resv_add_fence(&resv, f, usage);
if (dma_resv_test_signaled(&resv, usage)) {
KUNIT_FAIL(test, "Resv unexpectedly signaled");
goto err_unlock;
}
dma_fence_signal(f);
if (!dma_resv_test_signaled(&resv, usage)) {
KUNIT_FAIL(test, "Resv not reporting signaled");
goto err_unlock;
}
err_unlock:
dma_resv_unlock(&resv);
err_free:
dma_resv_fini(&resv);
dma_fence_put(f);
}
static void test_for_each(struct kunit *test)
{
const struct dma_resv_usage_param *param = test->param_value;
enum dma_resv_usage usage = param->usage;
struct dma_resv_iter cursor;
struct dma_fence *f, *fence;
struct dma_resv resv;
int r;
f = alloc_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_resv_init(&resv);
r = dma_resv_lock(&resv, NULL);
if (r) {
KUNIT_FAIL(test, "Resv locking failed");
goto err_free;
}
r = dma_resv_reserve_fences(&resv, 1);
if (r) {
KUNIT_FAIL(test, "Resv shared slot allocation failed");
goto err_unlock;
}
dma_resv_add_fence(&resv, f, usage);
r = -ENOENT;
dma_resv_for_each_fence(&cursor, &resv, usage, fence) {
if (!r) {
KUNIT_FAIL(test, "More than one fence found");
goto err_unlock;
}
if (f != fence) {
KUNIT_FAIL(test, "Unexpected fence");
r = -EINVAL;
goto err_unlock;
}
if (dma_resv_iter_usage(&cursor) != usage) {
KUNIT_FAIL(test, "Unexpected fence usage");
r = -EINVAL;
goto err_unlock;
}
r = 0;
}
if (r) {
KUNIT_FAIL(test, "No fence found");
goto err_unlock;
}
dma_fence_signal(f);
err_unlock:
dma_resv_unlock(&resv);
err_free:
dma_resv_fini(&resv);
dma_fence_put(f);
}
static void test_for_each_unlocked(struct kunit *test)
{
const struct dma_resv_usage_param *param = test->param_value;
enum dma_resv_usage usage = param->usage;
struct dma_resv_iter cursor;
struct dma_fence *f, *fence;
struct dma_resv resv;
int r;
f = alloc_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_resv_init(&resv);
r = dma_resv_lock(&resv, NULL);
if (r) {
KUNIT_FAIL(test, "Resv locking failed");
goto err_free;
}
r = dma_resv_reserve_fences(&resv, 1);
if (r) {
KUNIT_FAIL(test, "Resv shared slot allocation failed");
dma_resv_unlock(&resv);
goto err_free;
}
dma_resv_add_fence(&resv, f, usage);
dma_resv_unlock(&resv);
r = -ENOENT;
dma_resv_iter_begin(&cursor, &resv, usage);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
if (!r) {
KUNIT_FAIL(test, "More than one fence found");
goto err_iter_end;
}
if (!dma_resv_iter_is_restarted(&cursor)) {
KUNIT_FAIL(test, "No restart flag");
goto err_iter_end;
}
if (f != fence) {
KUNIT_FAIL(test, "Unexpected fence");
r = -EINVAL;
goto err_iter_end;
}
if (dma_resv_iter_usage(&cursor) != usage) {
KUNIT_FAIL(test, "Unexpected fence usage");
r = -EINVAL;
goto err_iter_end;
}
/* We use r as state here */
if (r == -ENOENT) {
r = -EINVAL;
/* That should trigger an restart */
cursor.fences = (void*)~0;
} else if (r == -EINVAL) {
r = 0;
}
}
KUNIT_EXPECT_EQ(test, r, 0);
err_iter_end:
dma_resv_iter_end(&cursor);
dma_fence_signal(f);
err_free:
dma_resv_fini(&resv);
dma_fence_put(f);
}
static void test_get_fences(struct kunit *test)
{
const struct dma_resv_usage_param *param = test->param_value;
enum dma_resv_usage usage = param->usage;
struct dma_fence *f, **fences = NULL;
struct dma_resv resv;
int r, i;
f = alloc_fence();
KUNIT_ASSERT_NOT_NULL(test, f);
dma_fence_enable_sw_signaling(f);
dma_resv_init(&resv);
r = dma_resv_lock(&resv, NULL);
if (r) {
KUNIT_FAIL(test, "Resv locking failed");
goto err_resv;
}
r = dma_resv_reserve_fences(&resv, 1);
if (r) {
KUNIT_FAIL(test, "Resv shared slot allocation failed");
dma_resv_unlock(&resv);
goto err_resv;
}
dma_resv_add_fence(&resv, f, usage);
dma_resv_unlock(&resv);
r = dma_resv_get_fences(&resv, usage, &i, &fences);
if (r) {
KUNIT_FAIL(test, "get_fences failed");
goto err_free;
}
if (i != 1 || fences[0] != f) {
KUNIT_FAIL(test, "get_fences returned unexpected fence");
goto err_free;
}
dma_fence_signal(f);
err_free:
while (i--)
dma_fence_put(fences[i]);
kfree(fences);
err_resv:
dma_resv_fini(&resv);
dma_fence_put(f);
}
static const struct dma_resv_usage_param dma_resv_usage_params[] = {
{ DMA_RESV_USAGE_KERNEL, "kernel" },
{ DMA_RESV_USAGE_WRITE, "write" },
{ DMA_RESV_USAGE_READ, "read" },
{ DMA_RESV_USAGE_BOOKKEEP, "bookkeep" },
};
KUNIT_ARRAY_PARAM_DESC(dma_resv_usage, dma_resv_usage_params, desc);
static struct kunit_case dma_resv_cases[] = {
KUNIT_CASE(test_sanitycheck),
KUNIT_CASE_PARAM(test_signaling, dma_resv_usage_gen_params),
KUNIT_CASE_PARAM(test_for_each, dma_resv_usage_gen_params),
KUNIT_CASE_PARAM(test_for_each_unlocked, dma_resv_usage_gen_params),
KUNIT_CASE_PARAM(test_get_fences, dma_resv_usage_gen_params),
{}
};
static struct kunit_suite dma_resv_test_suite = {
.name = "dma-buf-resv",
.test_cases = dma_resv_cases,
};
kunit_test_suite(dma_resv_test_suite);
MODULE_DESCRIPTION("KUnit tests for DMA-BUF");
MODULE_LICENSE("GPL");
+485
View File
@@ -0,0 +1,485 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Sync File validation framework
*
* Copyright (C) 2012 Google, Inc.
*/
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/panic.h>
#include <linux/slab.h>
#include <linux/sync_file.h>
#include "sync_debug.h"
#define CREATE_TRACE_POINTS
#include "sync_trace.h"
/*
* SW SYNC validation framework
*
* A sync object driver that uses a 32bit counter to coordinate
* synchronization. Useful when there is no hardware primitive backing
* the synchronization.
*
* To start the framework just open:
*
* <debugfs>/sync/sw_sync
*
* That will create a sync timeline, all fences created under this timeline
* file descriptor will belong to the this timeline.
*
* The 'sw_sync' file can be opened many times as to create different
* timelines.
*
* Fences can be created with SW_SYNC_IOC_CREATE_FENCE ioctl with struct
* sw_sync_create_fence_data as parameter.
*
* To increment the timeline counter, SW_SYNC_IOC_INC ioctl should be used
* with the increment as u32. This will update the last signaled value
* from the timeline and signal any fence that has a seqno smaller or equal
* to it.
*
* struct sw_sync_create_fence_data
* @value: the seqno to initialise the fence with
* @name: the name of the new sync point
* @fence: return the fd of the new sync_file with the created fence
*/
struct sw_sync_create_fence_data {
__u32 value;
char name[32];
__s32 fence; /* fd of new fence */
};
/**
* struct sw_sync_get_deadline - get the deadline hint of a sw_sync fence
* @deadline_ns: absolute time of the deadline
* @pad: must be zero
* @fence_fd: the sw_sync fence fd (in)
*
* Return the earliest deadline set on the fence. The timebase for the
* deadline is CLOCK_MONOTONIC (same as vblank). If there is no deadline
* set on the fence, this ioctl will return -ENOENT.
*/
struct sw_sync_get_deadline {
__u64 deadline_ns;
__u32 pad;
__s32 fence_fd;
};
#define SW_SYNC_IOC_MAGIC 'W'
#define SW_SYNC_IOC_CREATE_FENCE _IOWR(SW_SYNC_IOC_MAGIC, 0,\
struct sw_sync_create_fence_data)
#define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32)
#define SW_SYNC_GET_DEADLINE _IOWR(SW_SYNC_IOC_MAGIC, 2, \
struct sw_sync_get_deadline)
#define SW_SYNC_HAS_DEADLINE_BIT DMA_FENCE_FLAG_USER_BITS
static const struct dma_fence_ops timeline_fence_ops;
static inline struct sync_pt *dma_fence_to_sync_pt(struct dma_fence *fence)
{
if (fence->ops != &timeline_fence_ops)
return NULL;
return container_of(fence, struct sync_pt, base);
}
/**
* sync_timeline_create() - creates a sync object
* @name: sync_timeline name
*
* Creates a new sync_timeline. Returns the sync_timeline object or NULL in
* case of error.
*/
static struct sync_timeline *sync_timeline_create(const char *name)
{
struct sync_timeline *obj;
obj = kzalloc_obj(*obj);
if (!obj)
return NULL;
kref_init(&obj->kref);
obj->context = dma_fence_context_alloc(1);
strscpy(obj->name, name, sizeof(obj->name));
obj->pt_tree = RB_ROOT;
INIT_LIST_HEAD(&obj->pt_list);
spin_lock_init(&obj->lock);
sync_timeline_debug_add(obj);
return obj;
}
static void sync_timeline_free(struct kref *kref)
{
struct sync_timeline *obj =
container_of(kref, struct sync_timeline, kref);
sync_timeline_debug_remove(obj);
kfree(obj);
}
static void sync_timeline_get(struct sync_timeline *obj)
{
kref_get(&obj->kref);
}
static void sync_timeline_put(struct sync_timeline *obj)
{
kref_put(&obj->kref, sync_timeline_free);
}
static const char *timeline_fence_get_driver_name(struct dma_fence *fence)
{
return "sw_sync";
}
static const char *timeline_fence_get_timeline_name(struct dma_fence *fence)
{
struct sync_timeline *parent = dma_fence_parent(fence);
return parent->name;
}
static void timeline_fence_release(struct dma_fence *fence)
{
struct sync_pt *pt = dma_fence_to_sync_pt(fence);
struct sync_timeline *parent = dma_fence_parent(fence);
unsigned long flags;
dma_fence_lock_irqsave(fence, flags);
if (!list_empty(&pt->link)) {
list_del(&pt->link);
rb_erase(&pt->node, &parent->pt_tree);
}
dma_fence_unlock_irqrestore(fence, flags);
sync_timeline_put(parent);
dma_fence_free(fence);
}
static bool timeline_fence_signaled(struct dma_fence *fence)
{
struct sync_timeline *parent = dma_fence_parent(fence);
return !__dma_fence_is_later(fence, fence->seqno, parent->value);
}
static void timeline_fence_set_deadline(struct dma_fence *fence, ktime_t deadline)
{
struct sync_pt *pt = dma_fence_to_sync_pt(fence);
unsigned long flags;
dma_fence_lock_irqsave(fence, flags);
if (test_bit(SW_SYNC_HAS_DEADLINE_BIT, &fence->flags)) {
if (ktime_before(deadline, pt->deadline))
pt->deadline = deadline;
} else {
pt->deadline = deadline;
__set_bit(SW_SYNC_HAS_DEADLINE_BIT, &fence->flags);
}
dma_fence_unlock_irqrestore(fence, flags);
}
static const struct dma_fence_ops timeline_fence_ops = {
.get_driver_name = timeline_fence_get_driver_name,
.get_timeline_name = timeline_fence_get_timeline_name,
.signaled = timeline_fence_signaled,
.release = timeline_fence_release,
.set_deadline = timeline_fence_set_deadline,
};
/**
* sync_timeline_signal() - signal a status change on a sync_timeline
* @obj: sync_timeline to signal
* @inc: num to increment on timeline->value
*
* A sync implementation should call this any time one of it's fences
* has signaled or has an error condition.
*/
static void sync_timeline_signal(struct sync_timeline *obj, unsigned int inc)
{
LIST_HEAD(signalled);
struct sync_pt *pt, *next;
trace_sync_timeline(obj);
spin_lock_irq(&obj->lock);
obj->value += inc;
list_for_each_entry_safe(pt, next, &obj->pt_list, link) {
if (!timeline_fence_signaled(&pt->base))
break;
dma_fence_get(&pt->base);
list_move_tail(&pt->link, &signalled);
rb_erase(&pt->node, &obj->pt_tree);
dma_fence_signal_locked(&pt->base);
}
spin_unlock_irq(&obj->lock);
list_for_each_entry_safe(pt, next, &signalled, link) {
list_del_init(&pt->link);
dma_fence_put(&pt->base);
}
}
/**
* sync_pt_create() - creates a sync pt
* @obj: parent sync_timeline
* @value: value of the fence
*
* Creates a new sync_pt (fence) as a child of @parent. @size bytes will be
* allocated allowing for implementation specific data to be kept after
* the generic sync_timeline struct. Returns the sync_pt object or
* NULL in case of error.
*/
static struct sync_pt *sync_pt_create(struct sync_timeline *obj,
unsigned int value)
{
struct sync_pt *pt;
pt = kzalloc_obj(*pt);
if (!pt)
return NULL;
sync_timeline_get(obj);
dma_fence_init(&pt->base, &timeline_fence_ops, &obj->lock,
obj->context, value);
INIT_LIST_HEAD(&pt->link);
spin_lock_irq(&obj->lock);
if (!dma_fence_is_signaled_locked(&pt->base)) {
struct rb_node **p = &obj->pt_tree.rb_node;
struct rb_node *parent = NULL;
while (*p) {
struct sync_pt *other;
int cmp;
parent = *p;
other = rb_entry(parent, typeof(*pt), node);
cmp = value - other->base.seqno;
if (cmp > 0) {
p = &parent->rb_right;
} else if (cmp < 0) {
p = &parent->rb_left;
} else {
if (dma_fence_get_rcu(&other->base)) {
sync_timeline_put(obj);
kfree(pt);
pt = other;
goto unlock;
}
p = &parent->rb_left;
}
}
rb_link_node(&pt->node, parent, p);
rb_insert_color(&pt->node, &obj->pt_tree);
parent = rb_next(&pt->node);
list_add_tail(&pt->link,
parent ? &rb_entry(parent, typeof(*pt), node)->link : &obj->pt_list);
}
unlock:
spin_unlock_irq(&obj->lock);
return pt;
}
/*
* *WARNING*
*
* improper use of this can result in deadlocking kernel drivers from userspace.
*/
/* opening sw_sync create a new sync obj */
static int sw_sync_debugfs_open(struct inode *inode, struct file *file)
{
struct sync_timeline *obj;
char task_comm[TASK_COMM_LEN];
get_task_comm(task_comm, current);
obj = sync_timeline_create(task_comm);
if (!obj)
return -ENOMEM;
file->private_data = obj;
return 0;
}
static int sw_sync_debugfs_release(struct inode *inode, struct file *file)
{
struct sync_timeline *obj = file->private_data;
struct sync_pt *pt, *next;
spin_lock_irq(&obj->lock);
list_for_each_entry_safe(pt, next, &obj->pt_list, link) {
dma_fence_set_error(&pt->base, -ENOENT);
dma_fence_signal_locked(&pt->base);
}
spin_unlock_irq(&obj->lock);
sync_timeline_put(obj);
return 0;
}
static long sw_sync_ioctl_create_fence(struct sync_timeline *obj,
unsigned long arg)
{
int fd = get_unused_fd_flags(O_CLOEXEC);
int err;
struct sync_pt *pt;
struct sync_file *sync_file;
struct sw_sync_create_fence_data data;
/* SW sync fence are inherently unsafe and can deadlock the kernel */
add_taint(TAINT_SOFTLOCKUP, LOCKDEP_STILL_OK);
if (fd < 0)
return fd;
if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {
err = -EFAULT;
goto err;
}
pt = sync_pt_create(obj, data.value);
if (!pt) {
err = -ENOMEM;
goto err;
}
sync_file = sync_file_create(&pt->base);
dma_fence_put(&pt->base);
if (!sync_file) {
err = -ENOMEM;
goto err;
}
data.fence = fd;
if (copy_to_user((void __user *)arg, &data, sizeof(data))) {
fput(sync_file->file);
err = -EFAULT;
goto err;
}
fd_install(fd, sync_file->file);
return 0;
err:
put_unused_fd(fd);
return err;
}
static long sw_sync_ioctl_inc(struct sync_timeline *obj, unsigned long arg)
{
u32 value;
if (copy_from_user(&value, (void __user *)arg, sizeof(value)))
return -EFAULT;
while (value > INT_MAX) {
sync_timeline_signal(obj, INT_MAX);
value -= INT_MAX;
}
sync_timeline_signal(obj, value);
return 0;
}
static int sw_sync_ioctl_get_deadline(struct sync_timeline *obj, unsigned long arg)
{
struct sw_sync_get_deadline data;
struct dma_fence *fence;
unsigned long flags;
struct sync_pt *pt;
int ret = 0;
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
return -EFAULT;
if (data.deadline_ns || data.pad)
return -EINVAL;
fence = sync_file_get_fence(data.fence_fd);
if (!fence)
return -EINVAL;
pt = dma_fence_to_sync_pt(fence);
if (!pt) {
ret = -EINVAL;
goto put_fence;
}
dma_fence_lock_irqsave(fence, flags);
if (!test_bit(SW_SYNC_HAS_DEADLINE_BIT, &fence->flags)) {
ret = -ENOENT;
goto unlock;
}
data.deadline_ns = ktime_to_ns(pt->deadline);
dma_fence_unlock_irqrestore(fence, flags);
dma_fence_put(fence);
if (ret)
return ret;
if (copy_to_user((void __user *)arg, &data, sizeof(data)))
return -EFAULT;
return 0;
unlock:
dma_fence_unlock_irqrestore(fence, flags);
put_fence:
dma_fence_put(fence);
return ret;
}
static long sw_sync_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct sync_timeline *obj = file->private_data;
switch (cmd) {
case SW_SYNC_IOC_CREATE_FENCE:
return sw_sync_ioctl_create_fence(obj, arg);
case SW_SYNC_IOC_INC:
return sw_sync_ioctl_inc(obj, arg);
case SW_SYNC_GET_DEADLINE:
return sw_sync_ioctl_get_deadline(obj, arg);
default:
return -ENOTTY;
}
}
const struct file_operations sw_sync_debugfs_fops = {
.open = sw_sync_debugfs_open,
.release = sw_sync_debugfs_release,
.unlocked_ioctl = sw_sync_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Sync File validation framework and debug information
*
* Copyright (C) 2012 Google, Inc.
*/
#include <linux/debugfs.h>
#include "sync_debug.h"
static struct dentry *dbgfs;
static LIST_HEAD(sync_timeline_list_head);
static DEFINE_SPINLOCK(sync_timeline_list_lock);
void sync_timeline_debug_add(struct sync_timeline *obj)
{
unsigned long flags;
spin_lock_irqsave(&sync_timeline_list_lock, flags);
list_add_tail(&obj->sync_timeline_list, &sync_timeline_list_head);
spin_unlock_irqrestore(&sync_timeline_list_lock, flags);
}
void sync_timeline_debug_remove(struct sync_timeline *obj)
{
unsigned long flags;
spin_lock_irqsave(&sync_timeline_list_lock, flags);
list_del(&obj->sync_timeline_list);
spin_unlock_irqrestore(&sync_timeline_list_lock, flags);
}
static const char *sync_status_str(int status)
{
if (status < 0)
return "error";
if (status > 0)
return "signaled";
return "active";
}
static void sync_print_fence(struct seq_file *s,
struct dma_fence *fence, bool show)
{
struct sync_timeline *parent = dma_fence_parent(fence);
int status;
status = dma_fence_get_status_locked(fence);
seq_printf(s, " %s%sfence %s",
show ? parent->name : "",
show ? "_" : "",
sync_status_str(status));
if (test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags)) {
struct timespec64 ts64 =
ktime_to_timespec64(fence->timestamp);
seq_printf(s, "@%ptSp", &ts64);
}
seq_printf(s, ": %lld", fence->seqno);
seq_printf(s, " / %d", parent->value);
seq_putc(s, '\n');
}
static void sync_print_obj(struct seq_file *s, struct sync_timeline *obj)
{
struct list_head *pos;
seq_printf(s, "%s: %d\n", obj->name, obj->value);
spin_lock(&obj->lock); /* Caller already disabled IRQ. */
list_for_each(pos, &obj->pt_list) {
struct sync_pt *pt = container_of(pos, struct sync_pt, link);
sync_print_fence(s, &pt->base, false);
}
spin_unlock(&obj->lock);
}
static int sync_info_debugfs_show(struct seq_file *s, void *unused)
{
struct list_head *pos;
seq_puts(s, "objs:\n--------------\n");
spin_lock_irq(&sync_timeline_list_lock);
list_for_each(pos, &sync_timeline_list_head) {
struct sync_timeline *obj =
container_of(pos, struct sync_timeline,
sync_timeline_list);
sync_print_obj(s, obj);
seq_putc(s, '\n');
}
spin_unlock_irq(&sync_timeline_list_lock);
seq_puts(s, "fences:\n--------------\n");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(sync_info_debugfs);
static __init int sync_debugfs_init(void)
{
dbgfs = debugfs_create_dir("sync", NULL);
/*
* The debugfs files won't ever get removed and thus, there is
* no need to protect it against removal races. The use of
* debugfs_create_file_unsafe() is actually safe here.
*/
debugfs_create_file_unsafe("info", 0444, dbgfs, NULL,
&sync_info_debugfs_fops);
debugfs_create_file_unsafe("sw_sync", 0644, dbgfs, NULL,
&sw_sync_debugfs_fops);
return 0;
}
late_initcall(sync_debugfs_init);
+72
View File
@@ -0,0 +1,72 @@
/*
* Sync File validation framework and debug infomation
*
* Copyright (C) 2012 Google, Inc.
*
* This program is distributed in the hope that 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.
*
*/
#ifndef _LINUX_SYNC_H
#define _LINUX_SYNC_H
#include <linux/list.h>
#include <linux/rbtree.h>
#include <linux/spinlock.h>
#include <linux/dma-fence.h>
#include <linux/sync_file.h>
#include <uapi/linux/sync_file.h>
/**
* struct sync_timeline - sync object
* @kref: reference count on fence.
* @name: name of the sync_timeline. Useful for debugging
* @lock: lock protecting @pt_list and @value
* @pt_tree: rbtree of active (unsignaled/errored) sync_pts
* @pt_list: list of active (unsignaled/errored) sync_pts
* @sync_timeline_list: membership in global sync_timeline_list
*/
struct sync_timeline {
struct kref kref;
char name[32];
/* protected by lock */
u64 context;
int value;
struct rb_root pt_tree;
struct list_head pt_list;
spinlock_t lock;
struct list_head sync_timeline_list;
};
static inline struct sync_timeline *dma_fence_parent(struct dma_fence *fence)
{
return container_of(fence->extern_lock, struct sync_timeline, lock);
}
/**
* struct sync_pt - sync_pt object
* @base: base fence object
* @link: link on the sync timeline's list
* @node: node in the sync timeline's tree
* @deadline: the earliest fence deadline hint
*/
struct sync_pt {
struct dma_fence base;
struct list_head link;
struct rb_node node;
ktime_t deadline;
};
extern const struct file_operations sw_sync_debugfs_fops;
void sync_timeline_debug_add(struct sync_timeline *obj);
void sync_timeline_debug_remove(struct sync_timeline *obj);
#endif /* _LINUX_SYNC_H */
+407
View File
@@ -0,0 +1,407 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/dma-buf/sync_file.c
*
* Copyright (C) 2012 Google, Inc.
*/
#include <linux/dma-fence-unwrap.h>
#include <linux/export.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/anon_inodes.h>
#include <linux/sync_file.h>
#include <uapi/linux/sync_file.h>
static const struct file_operations sync_file_fops;
static struct sync_file *sync_file_alloc(void)
{
struct sync_file *sync_file;
sync_file = kzalloc_obj(*sync_file);
if (!sync_file)
return NULL;
sync_file->file = anon_inode_getfile("sync_file", &sync_file_fops,
sync_file, 0);
if (IS_ERR(sync_file->file))
goto err;
init_waitqueue_head(&sync_file->wq);
INIT_LIST_HEAD(&sync_file->cb.node);
return sync_file;
err:
kfree(sync_file);
return NULL;
}
static void fence_check_cb_func(struct dma_fence *f, struct dma_fence_cb *cb)
{
struct sync_file *sync_file;
sync_file = container_of(cb, struct sync_file, cb);
wake_up_all(&sync_file->wq);
}
/**
* sync_file_create() - creates a sync file
* @fence: fence to add to the sync_fence
*
* Creates a sync_file containg @fence. This function acquires and additional
* reference of @fence for the newly-created &sync_file, if it succeeds. The
* sync_file can be released with fput(sync_file->file). Returns the
* sync_file or NULL in case of error.
*/
struct sync_file *sync_file_create(struct dma_fence *fence)
{
struct sync_file *sync_file;
sync_file = sync_file_alloc();
if (!sync_file)
return NULL;
sync_file->fence = dma_fence_get(fence);
return sync_file;
}
EXPORT_SYMBOL(sync_file_create);
static struct sync_file *sync_file_fdget(int fd)
{
struct file *file = fget(fd);
if (!file)
return NULL;
if (file->f_op != &sync_file_fops)
goto err;
return file->private_data;
err:
fput(file);
return NULL;
}
/**
* sync_file_get_fence - get the fence related to the sync_file fd
* @fd: sync_file fd to get the fence from
*
* Ensures @fd references a valid sync_file and returns a fence that
* represents all fence in the sync_file. On error NULL is returned.
*/
struct dma_fence *sync_file_get_fence(int fd)
{
struct sync_file *sync_file;
struct dma_fence *fence;
sync_file = sync_file_fdget(fd);
if (!sync_file)
return NULL;
fence = dma_fence_get(sync_file->fence);
fput(sync_file->file);
return fence;
}
EXPORT_SYMBOL(sync_file_get_fence);
/**
* sync_file_get_name - get the name of the sync_file
* @sync_file: sync_file to get the fence from
* @buf: destination buffer to copy sync_file name into
* @len: available size of destination buffer.
*
* Each sync_file may have a name assigned either by the user (when merging
* sync_files together) or created from the fence it contains. In the latter
* case construction of the name is deferred until use, and so requires
* sync_file_get_name().
*
* Returns: a string representing the name.
*/
char *sync_file_get_name(struct sync_file *sync_file, char *buf, int len)
{
if (sync_file->user_name[0]) {
strscpy(buf, sync_file->user_name, len);
} else {
struct dma_fence *fence = sync_file->fence;
const char __rcu *timeline;
const char __rcu *driver;
rcu_read_lock();
driver = dma_fence_driver_name(fence);
timeline = dma_fence_timeline_name(fence);
snprintf(buf, len, "%s-%s%llu-%lld",
rcu_dereference(driver),
rcu_dereference(timeline),
fence->context,
fence->seqno);
rcu_read_unlock();
}
return buf;
}
/**
* sync_file_merge() - merge two sync_files
* @name: name of new fence
* @a: sync_file a
* @b: sync_file b
*
* Creates a new sync_file which contains copies of all the fences in both
* @a and @b. @a and @b remain valid, independent sync_file. Returns the
* new merged sync_file or NULL in case of error.
*/
static struct sync_file *sync_file_merge(const char *name, struct sync_file *a,
struct sync_file *b)
{
struct sync_file *sync_file;
struct dma_fence *fence;
sync_file = sync_file_alloc();
if (!sync_file)
return NULL;
fence = dma_fence_unwrap_merge(a->fence, b->fence);
if (!fence) {
fput(sync_file->file);
return NULL;
}
sync_file->fence = fence;
strscpy(sync_file->user_name, name, sizeof(sync_file->user_name));
return sync_file;
}
static int sync_file_release(struct inode *inode, struct file *file)
{
struct sync_file *sync_file = file->private_data;
if (test_bit(POLL_ENABLED, &sync_file->flags))
dma_fence_remove_callback(sync_file->fence, &sync_file->cb);
dma_fence_put(sync_file->fence);
kfree(sync_file);
return 0;
}
static __poll_t sync_file_poll(struct file *file, poll_table *wait)
{
struct sync_file *sync_file = file->private_data;
poll_wait(file, &sync_file->wq, wait);
if (list_empty(&sync_file->cb.node) &&
!test_and_set_bit(POLL_ENABLED, &sync_file->flags)) {
if (dma_fence_add_callback(sync_file->fence, &sync_file->cb,
fence_check_cb_func) < 0)
wake_up_all(&sync_file->wq);
}
return dma_fence_is_signaled(sync_file->fence) ? EPOLLIN : 0;
}
static long sync_file_ioctl_merge(struct sync_file *sync_file,
unsigned long arg)
{
int fd = get_unused_fd_flags(O_CLOEXEC);
int err;
struct sync_file *fence2, *fence3;
struct sync_merge_data data;
if (fd < 0)
return fd;
if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {
err = -EFAULT;
goto err_put_fd;
}
if (data.flags || data.pad) {
err = -EINVAL;
goto err_put_fd;
}
fence2 = sync_file_fdget(data.fd2);
if (!fence2) {
err = -ENOENT;
goto err_put_fd;
}
data.name[sizeof(data.name) - 1] = '\0';
fence3 = sync_file_merge(data.name, sync_file, fence2);
if (!fence3) {
err = -ENOMEM;
goto err_put_fence2;
}
data.fence = fd;
if (copy_to_user((void __user *)arg, &data, sizeof(data))) {
err = -EFAULT;
goto err_put_fence3;
}
fd_install(fd, fence3->file);
fput(fence2->file);
return 0;
err_put_fence3:
fput(fence3->file);
err_put_fence2:
fput(fence2->file);
err_put_fd:
put_unused_fd(fd);
return err;
}
static int sync_fill_fence_info(struct dma_fence *fence,
struct sync_fence_info *info)
{
const char __rcu *timeline;
const char __rcu *driver;
rcu_read_lock();
driver = dma_fence_driver_name(fence);
timeline = dma_fence_timeline_name(fence);
strscpy(info->obj_name, rcu_dereference(timeline),
sizeof(info->obj_name));
strscpy(info->driver_name, rcu_dereference(driver),
sizeof(info->driver_name));
info->status = dma_fence_get_status(fence);
info->timestamp_ns =
dma_fence_is_signaled(fence) ?
ktime_to_ns(dma_fence_timestamp(fence)) :
ktime_set(0, 0);
rcu_read_unlock();
return info->status;
}
static long sync_file_ioctl_fence_info(struct sync_file *sync_file,
unsigned long arg)
{
struct sync_fence_info *fence_info = NULL;
struct dma_fence_unwrap iter;
struct sync_file_info info;
unsigned int num_fences;
struct dma_fence *fence;
int ret;
__u32 size;
if (copy_from_user(&info, (void __user *)arg, sizeof(info)))
return -EFAULT;
if (info.flags || info.pad)
return -EINVAL;
num_fences = 0;
dma_fence_unwrap_for_each(fence, &iter, sync_file->fence)
++num_fences;
/*
* Passing num_fences = 0 means that userspace doesn't want to
* retrieve any sync_fence_info. If num_fences = 0 we skip filling
* sync_fence_info and return the actual number of fences on
* info->num_fences.
*/
if (!info.num_fences) {
info.status = dma_fence_get_status(sync_file->fence);
goto no_fences;
} else {
info.status = 1;
}
if (info.num_fences < num_fences)
return -EINVAL;
size = num_fences * sizeof(*fence_info);
fence_info = kzalloc(size, GFP_KERNEL);
if (!fence_info)
return -ENOMEM;
num_fences = 0;
dma_fence_unwrap_for_each(fence, &iter, sync_file->fence) {
int status;
status = sync_fill_fence_info(fence, &fence_info[num_fences++]);
info.status = info.status <= 0 ? info.status : status;
}
if (copy_to_user(u64_to_user_ptr(info.sync_fence_info), fence_info,
size)) {
ret = -EFAULT;
goto out;
}
no_fences:
sync_file_get_name(sync_file, info.name, sizeof(info.name));
info.num_fences = num_fences;
if (copy_to_user((void __user *)arg, &info, sizeof(info)))
ret = -EFAULT;
else
ret = 0;
out:
kfree(fence_info);
return ret;
}
static int sync_file_ioctl_set_deadline(struct sync_file *sync_file,
unsigned long arg)
{
struct sync_set_deadline ts;
if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
return -EFAULT;
if (ts.pad)
return -EINVAL;
dma_fence_set_deadline(sync_file->fence, ns_to_ktime(ts.deadline_ns));
return 0;
}
static long sync_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct sync_file *sync_file = file->private_data;
switch (cmd) {
case SYNC_IOC_MERGE:
return sync_file_ioctl_merge(sync_file, arg);
case SYNC_IOC_FILE_INFO:
return sync_file_ioctl_fence_info(sync_file, arg);
case SYNC_IOC_SET_DEADLINE:
return sync_file_ioctl_set_deadline(sync_file, arg);
default:
return -ENOTTY;
}
}
static const struct file_operations sync_file_fops = {
.release = sync_file_release,
.poll = sync_file_poll,
.unlocked_ioctl = sync_file_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
+33
View File
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_INCLUDE_PATH ../../drivers/dma-buf
#define TRACE_SYSTEM sync_trace
#if !defined(_TRACE_SYNC_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SYNC_H
#include "sync_debug.h"
#include <linux/tracepoint.h>
TRACE_EVENT(sync_timeline,
TP_PROTO(struct sync_timeline *timeline),
TP_ARGS(timeline),
TP_STRUCT__entry(
__string(name, timeline->name)
__field(u32, value)
),
TP_fast_assign(
__assign_str(name);
__entry->value = timeline->value;
),
TP_printk("name=%s value=%d", __get_str(name), __entry->value)
);
#endif /* if !defined(_TRACE_SYNC_H) || defined(TRACE_HEADER_MULTI_READ) */
/* This part must be outside protection */
#include <trace/define_trace.h>
+551
View File
@@ -0,0 +1,551 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/cred.h>
#include <linux/device.h>
#include <linux/dma-buf.h>
#include <linux/dma-resv.h>
#include <linux/highmem.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/memfd.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/shmem_fs.h>
#include <linux/hugetlb.h>
#include <linux/slab.h>
#include <linux/udmabuf.h>
#include <linux/vmalloc.h>
#include <linux/iosys-map.h>
static int list_limit = 1024;
module_param(list_limit, int, 0644);
MODULE_PARM_DESC(list_limit, "udmabuf_create_list->count limit. Default is 1024.");
static int size_limit_mb = 64;
module_param(size_limit_mb, int, 0644);
MODULE_PARM_DESC(size_limit_mb, "Max size of a dmabuf, in megabytes. Default is 64.");
struct udmabuf {
pgoff_t pagecount;
struct page **pages;
/**
* Unlike pages, pinned_folios is only used for unpin.
* So, nr_pinned is not the same to pagecount, the pinned_folios
* only set each folio which already pinned when udmabuf_create.
* Note that, since a folio may be pinned multiple times, each folio
* can be added to pinned_folios multiple times, depending on how many
* times the folio has been pinned when create.
*/
pgoff_t nr_pinned;
struct folio **pinned_folios;
struct sg_table *sg;
enum dma_data_direction sg_dir;
struct miscdevice *device;
};
static vm_fault_t udmabuf_vm_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct udmabuf *ubuf = vma->vm_private_data;
pgoff_t pgoff = vmf->pgoff;
unsigned long addr, pfn;
vm_fault_t ret;
if (pgoff >= ubuf->pagecount)
return VM_FAULT_SIGBUS;
pfn = page_to_pfn(ubuf->pages[pgoff]);
ret = vmf_insert_pfn(vma, vmf->address, pfn);
if (ret & VM_FAULT_ERROR)
return ret;
/* pre fault */
pgoff = vma->vm_pgoff;
addr = vma->vm_start;
for (; addr < vma->vm_end; pgoff++, addr += PAGE_SIZE) {
if (addr == vmf->address)
continue;
if (WARN_ON(pgoff >= ubuf->pagecount))
break;
pfn = page_to_pfn(ubuf->pages[pgoff]);
/**
* If the below vmf_insert_pfn() fails, we do not return an
* error here during this pre-fault step. However, an error
* will be returned if the failure occurs when the addr is
* truly accessed.
*/
if (vmf_insert_pfn(vma, addr, pfn) & VM_FAULT_ERROR)
break;
}
return ret;
}
static const struct vm_operations_struct udmabuf_vm_ops = {
.fault = udmabuf_vm_fault,
};
static int mmap_udmabuf(struct dma_buf *buf, struct vm_area_struct *vma)
{
struct udmabuf *ubuf = buf->priv;
if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0)
return -EINVAL;
vma->vm_ops = &udmabuf_vm_ops;
vma->vm_private_data = ubuf;
vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
return 0;
}
static int vmap_udmabuf(struct dma_buf *buf, struct iosys_map *map)
{
struct udmabuf *ubuf = buf->priv;
void *vaddr;
dma_resv_assert_held(buf->resv);
vaddr = vm_map_ram(ubuf->pages, ubuf->pagecount, -1);
if (!vaddr)
return -EINVAL;
iosys_map_set_vaddr(map, vaddr);
return 0;
}
static void vunmap_udmabuf(struct dma_buf *buf, struct iosys_map *map)
{
struct udmabuf *ubuf = buf->priv;
dma_resv_assert_held(buf->resv);
vm_unmap_ram(map->vaddr, ubuf->pagecount);
}
static struct sg_table *get_sg_table(struct device *dev, struct dma_buf *buf,
enum dma_data_direction direction)
{
struct udmabuf *ubuf = buf->priv;
struct sg_table *sg;
int ret;
sg = kzalloc_obj(*sg);
if (!sg)
return ERR_PTR(-ENOMEM);
ret = sg_alloc_table_from_pages(sg, ubuf->pages, ubuf->pagecount, 0,
ubuf->pagecount << PAGE_SHIFT,
GFP_KERNEL);
if (ret < 0)
goto err_alloc;
ret = dma_map_sgtable(dev, sg, direction, DMA_ATTR_SKIP_CPU_SYNC);
if (ret < 0)
goto err_map;
return sg;
err_map:
sg_free_table(sg);
err_alloc:
kfree(sg);
return ERR_PTR(ret);
}
static void put_sg_table(struct device *dev, struct sg_table *sg,
enum dma_data_direction direction)
{
dma_unmap_sgtable(dev, sg, direction, DMA_ATTR_SKIP_CPU_SYNC);
sg_free_table(sg);
kfree(sg);
}
static struct sg_table *map_udmabuf(struct dma_buf_attachment *at,
enum dma_data_direction direction)
{
return get_sg_table(at->dev, at->dmabuf, direction);
}
static void unmap_udmabuf(struct dma_buf_attachment *at,
struct sg_table *sg,
enum dma_data_direction direction)
{
return put_sg_table(at->dev, sg, direction);
}
static void unpin_all_folios(struct udmabuf *ubuf)
{
pgoff_t i;
for (i = 0; i < ubuf->nr_pinned; ++i)
unpin_folio(ubuf->pinned_folios[i]);
kvfree(ubuf->pinned_folios);
}
static __always_inline int init_udmabuf(struct udmabuf *ubuf, pgoff_t pgcnt)
{
ubuf->pages = kvmalloc_objs(*ubuf->pages, pgcnt);
if (!ubuf->pages)
return -ENOMEM;
ubuf->pinned_folios = kvmalloc_objs(*ubuf->pinned_folios, pgcnt);
if (!ubuf->pinned_folios)
return -ENOMEM;
return 0;
}
static __always_inline void deinit_udmabuf(struct udmabuf *ubuf)
{
unpin_all_folios(ubuf);
kvfree(ubuf->pages);
}
static void release_udmabuf(struct dma_buf *buf)
{
struct udmabuf *ubuf = buf->priv;
struct device *dev = ubuf->device->this_device;
if (ubuf->sg)
put_sg_table(dev, ubuf->sg, ubuf->sg_dir);
deinit_udmabuf(ubuf);
kfree(ubuf);
}
static int begin_cpu_udmabuf(struct dma_buf *buf,
enum dma_data_direction direction)
{
struct udmabuf *ubuf = buf->priv;
struct device *dev = ubuf->device->this_device;
if (!ubuf->sg) {
ubuf->sg = get_sg_table(dev, buf, direction);
if (IS_ERR(ubuf->sg)) {
int ret;
ret = PTR_ERR(ubuf->sg);
ubuf->sg = NULL;
return ret;
} else {
ubuf->sg_dir = direction;
}
}
dma_sync_sgtable_for_cpu(dev, ubuf->sg, direction);
return 0;
}
static int end_cpu_udmabuf(struct dma_buf *buf,
enum dma_data_direction direction)
{
struct udmabuf *ubuf = buf->priv;
struct device *dev = ubuf->device->this_device;
if (!ubuf->sg)
return -EINVAL;
dma_sync_sgtable_for_device(dev, ubuf->sg, direction);
return 0;
}
static const struct dma_buf_ops udmabuf_ops = {
.map_dma_buf = map_udmabuf,
.unmap_dma_buf = unmap_udmabuf,
.release = release_udmabuf,
.mmap = mmap_udmabuf,
.vmap = vmap_udmabuf,
.vunmap = vunmap_udmabuf,
.begin_cpu_access = begin_cpu_udmabuf,
.end_cpu_access = end_cpu_udmabuf,
};
#define SEALS_WANTED (F_SEAL_SHRINK)
#define SEALS_DENIED (F_SEAL_WRITE|F_SEAL_FUTURE_WRITE)
static int check_memfd_seals(struct file *memfd)
{
int seals;
if (!shmem_file(memfd) && !is_file_hugepages(memfd))
return -EBADFD;
seals = memfd_fcntl(memfd, F_GET_SEALS, 0);
if (seals == -EINVAL)
return -EBADFD;
if ((seals & SEALS_WANTED) != SEALS_WANTED ||
(seals & SEALS_DENIED) != 0)
return -EINVAL;
return 0;
}
static struct dma_buf *export_udmabuf(struct udmabuf *ubuf,
struct miscdevice *device)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
ubuf->device = device;
exp_info.ops = &udmabuf_ops;
exp_info.size = ubuf->pagecount << PAGE_SHIFT;
exp_info.priv = ubuf;
exp_info.flags = O_RDWR;
return dma_buf_export(&exp_info);
}
static long udmabuf_pin_folios(struct udmabuf *ubuf, struct file *memfd,
loff_t start, loff_t size, struct folio **folios)
{
pgoff_t nr_pinned = ubuf->nr_pinned;
pgoff_t upgcnt = ubuf->pagecount;
u32 cur_folio, cur_pgcnt;
pgoff_t pgoff, pgcnt;
long nr_folios;
loff_t end;
pgcnt = size >> PAGE_SHIFT;
end = start + (pgcnt << PAGE_SHIFT) - 1;
nr_folios = memfd_pin_folios(memfd, start, end, folios, pgcnt, &pgoff);
if (nr_folios <= 0)
return nr_folios ? nr_folios : -EINVAL;
cur_pgcnt = 0;
for (cur_folio = 0; cur_folio < nr_folios; ++cur_folio) {
pgoff_t subpgoff = pgoff;
size_t fsize = folio_size(folios[cur_folio]);
ubuf->pinned_folios[nr_pinned++] = folios[cur_folio];
for (; subpgoff < fsize; subpgoff += PAGE_SIZE) {
ubuf->pages[upgcnt] = folio_page(folios[cur_folio],
subpgoff >> PAGE_SHIFT);
++upgcnt;
if (++cur_pgcnt >= pgcnt)
goto end;
}
/**
* In a given range, only the first subpage of the first folio
* has an offset, that is returned by memfd_pin_folios().
* The first subpages of other folios (in the range) have an
* offset of 0.
*/
pgoff = 0;
}
end:
ubuf->pagecount = upgcnt;
ubuf->nr_pinned = nr_pinned;
return 0;
}
static long udmabuf_create(struct miscdevice *device,
struct udmabuf_create_list *head,
struct udmabuf_create_item *list)
{
unsigned long max_nr_folios = 0;
struct folio **folios = NULL;
pgoff_t pgcnt = 0, pglimit;
struct udmabuf *ubuf;
struct dma_buf *dmabuf;
long ret = -EINVAL;
u32 i, flags;
ubuf = kzalloc(sizeof(*ubuf), GFP_KERNEL);
if (!ubuf)
return -ENOMEM;
pglimit = ((u64)size_limit_mb * 1024 * 1024) >> PAGE_SHIFT;
for (i = 0; i < head->count; i++) {
pgoff_t subpgcnt;
if (!PAGE_ALIGNED(list[i].offset))
goto err_noinit;
if (!PAGE_ALIGNED(list[i].size))
goto err_noinit;
subpgcnt = list[i].size >> PAGE_SHIFT;
pgcnt += subpgcnt;
if (pgcnt > pglimit)
goto err_noinit;
max_nr_folios = max_t(unsigned long, subpgcnt, max_nr_folios);
}
if (!pgcnt)
goto err_noinit;
ret = init_udmabuf(ubuf, pgcnt);
if (ret)
goto err;
folios = kvmalloc_array(max_nr_folios, sizeof(*folios), GFP_KERNEL);
if (!folios) {
ret = -ENOMEM;
goto err;
}
for (i = 0; i < head->count; i++) {
struct file *memfd = fget(list[i].memfd);
if (!memfd) {
ret = -EBADFD;
goto err;
}
/*
* Take the inode lock to protect against concurrent
* memfd_add_seals(), which takes this lock in write mode.
*/
inode_lock_shared(file_inode(memfd));
ret = check_memfd_seals(memfd);
if (ret)
goto out_unlock;
ret = udmabuf_pin_folios(ubuf, memfd, list[i].offset,
list[i].size, folios);
out_unlock:
inode_unlock_shared(file_inode(memfd));
fput(memfd);
if (ret)
goto err;
}
flags = head->flags & UDMABUF_FLAGS_CLOEXEC ? O_CLOEXEC : 0;
dmabuf = export_udmabuf(ubuf, device);
if (IS_ERR(dmabuf)) {
ret = PTR_ERR(dmabuf);
goto err;
}
/*
* Ownership of ubuf is held by the dmabuf from here.
* If the following dma_buf_fd() fails, dma_buf_put() cleans up both the
* dmabuf and the ubuf (through udmabuf_ops.release).
*/
ret = dma_buf_fd(dmabuf, flags);
if (ret < 0)
dma_buf_put(dmabuf);
kvfree(folios);
return ret;
err:
deinit_udmabuf(ubuf);
err_noinit:
kfree(ubuf);
kvfree(folios);
return ret;
}
static long udmabuf_ioctl_create(struct file *filp, unsigned long arg)
{
struct udmabuf_create create;
struct udmabuf_create_list head;
struct udmabuf_create_item list;
if (copy_from_user(&create, (void __user *)arg,
sizeof(create)))
return -EFAULT;
head.flags = create.flags;
head.count = 1;
list.memfd = create.memfd;
list.offset = create.offset;
list.size = create.size;
return udmabuf_create(filp->private_data, &head, &list);
}
static long udmabuf_ioctl_create_list(struct file *filp, unsigned long arg)
{
struct udmabuf_create_list head;
struct udmabuf_create_item *list;
int ret = -EINVAL;
u32 lsize;
if (copy_from_user(&head, (void __user *)arg, sizeof(head)))
return -EFAULT;
if (head.count > list_limit)
return -EINVAL;
lsize = sizeof(struct udmabuf_create_item) * head.count;
list = memdup_user((void __user *)(arg + sizeof(head)), lsize);
if (IS_ERR(list))
return PTR_ERR(list);
ret = udmabuf_create(filp->private_data, &head, list);
kfree(list);
return ret;
}
static long udmabuf_ioctl(struct file *filp, unsigned int ioctl,
unsigned long arg)
{
long ret;
switch (ioctl) {
case UDMABUF_CREATE:
ret = udmabuf_ioctl_create(filp, arg);
break;
case UDMABUF_CREATE_LIST:
ret = udmabuf_ioctl_create_list(filp, arg);
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
static const struct file_operations udmabuf_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = udmabuf_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = udmabuf_ioctl,
#endif
};
static struct miscdevice udmabuf_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "udmabuf",
.fops = &udmabuf_fops,
};
static int __init udmabuf_dev_init(void)
{
int ret;
ret = misc_register(&udmabuf_misc);
if (ret < 0) {
pr_err("Could not initialize udmabuf device\n");
return ret;
}
ret = dma_coerce_mask_and_coherent(udmabuf_misc.this_device,
DMA_BIT_MASK(64));
if (ret < 0) {
pr_err("Could not setup DMA mask for udmabuf device\n");
misc_deregister(&udmabuf_misc);
return ret;
}
return 0;
}
static void __exit udmabuf_dev_exit(void)
{
misc_deregister(&udmabuf_misc);
}
module_init(udmabuf_dev_init)
module_exit(udmabuf_dev_exit)
MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");