4e63ce6af6
The new unified memory manager uses page offset to pass buffer handle during the mmap operation. One problem with this approach is that it requires the handle to always be divisible by the page size, else, the user would not be able to pass it correctly as an argument to the mmap system call. Previously, this was achieved by shifting the handle left after alloc operation, and shifting it right before get operation. This was done in the user code. This creates code duplication, and, what's worse, requires some knowledge from the user regarding the handle internal structure, hurting the encapsulation. This patch encloses all the page shifts inside memory manager functions. This way, the user can take the handle as a black box, and simply use it, without any concert about how it actually works. Signed-off-by: Yuri Nudelman <ynudelman@habana.ai> Reviewed-by: Oded Gabbay <ogabbay@kernel.org> Signed-off-by: Oded Gabbay <ogabbay@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
281 lines
6.6 KiB
C
281 lines
6.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
* Copyright 2022 HabanaLabs, Ltd.
|
|
* All Rights Reserved.
|
|
*/
|
|
|
|
#include "habanalabs.h"
|
|
|
|
/**
|
|
* hl_mmap_mem_buf_get - increase the buffer refcount and return a pointer to
|
|
* the buffer descriptor.
|
|
*
|
|
* @mmg: parent unifed memory manager
|
|
* @handle: requested buffer handle
|
|
*
|
|
* @return Find the buffer in the store and return a pointer to its descriptor.
|
|
* Increase buffer refcount. If not found - return NULL.
|
|
*/
|
|
struct hl_mmap_mem_buf *hl_mmap_mem_buf_get(struct hl_mem_mgr *mmg, u64 handle)
|
|
{
|
|
struct hl_mmap_mem_buf *buf;
|
|
|
|
spin_lock(&mmg->lock);
|
|
buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
|
|
if (!buf) {
|
|
spin_unlock(&mmg->lock);
|
|
dev_warn(mmg->dev,
|
|
"Buff get failed, no match to handle %llu\n", handle);
|
|
return NULL;
|
|
}
|
|
kref_get(&buf->refcount);
|
|
spin_unlock(&mmg->lock);
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* hl_mmap_mem_buf_release - release buffer
|
|
*
|
|
* @kref: kref that reached 0.
|
|
*
|
|
* Internal function, used as a kref release callback, when the last user of
|
|
* the buffer is released. Shall be called from an interrupt context.
|
|
*/
|
|
static void hl_mmap_mem_buf_release(struct kref *kref)
|
|
{
|
|
struct hl_mmap_mem_buf *buf =
|
|
container_of(kref, struct hl_mmap_mem_buf, refcount);
|
|
|
|
spin_lock(&buf->mmg->lock);
|
|
idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
|
|
spin_unlock(&buf->mmg->lock);
|
|
|
|
if (buf->behavior->release)
|
|
buf->behavior->release(buf);
|
|
|
|
kfree(buf);
|
|
}
|
|
|
|
/**
|
|
* hl_mmap_mem_buf_put - decrease the reference to the buffer
|
|
*
|
|
* @buf: memory manager buffer descriptor
|
|
*
|
|
* Decrease the reference to the buffer, and release it if it was the last one.
|
|
* Shall be called from an interrupt context.
|
|
*/
|
|
int hl_mmap_mem_buf_put(struct hl_mmap_mem_buf *buf)
|
|
{
|
|
return kref_put(&buf->refcount, hl_mmap_mem_buf_release);
|
|
}
|
|
|
|
/**
|
|
* hl_mmap_mem_buf_alloc - allocate a new mappable buffer
|
|
*
|
|
* @mmg: parent unifed memory manager
|
|
* @behavior: behavior object describing this buffer polymorphic behavior
|
|
* @gfp: gfp flags to use for the memory allocations
|
|
* @args: additional args passed to behavior->alloc
|
|
*
|
|
* Allocate and register a new memory buffer inside the give memory manager.
|
|
* Return the pointer to the new buffer on success or NULL on failure.
|
|
*/
|
|
struct hl_mmap_mem_buf *
|
|
hl_mmap_mem_buf_alloc(struct hl_mem_mgr *mmg,
|
|
struct hl_mmap_mem_buf_behavior *behavior, gfp_t gfp,
|
|
void *args)
|
|
{
|
|
struct hl_mmap_mem_buf *buf;
|
|
int rc;
|
|
|
|
buf = kzalloc(sizeof(*buf), gfp);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
spin_lock(&mmg->lock);
|
|
rc = idr_alloc(&mmg->handles, buf, 1, 0, GFP_ATOMIC);
|
|
spin_unlock(&mmg->lock);
|
|
if (rc < 0) {
|
|
dev_err(mmg->dev,
|
|
"Failed to allocate IDR for a new buffer, rc=%d\n", rc);
|
|
goto free_buf;
|
|
}
|
|
|
|
buf->mmg = mmg;
|
|
buf->behavior = behavior;
|
|
buf->handle = (((u64)rc | buf->behavior->mem_id) << PAGE_SHIFT);
|
|
kref_init(&buf->refcount);
|
|
|
|
rc = buf->behavior->alloc(buf, gfp, args);
|
|
if (rc) {
|
|
dev_err(mmg->dev, "Failure in buffer alloc callback %d\n",
|
|
rc);
|
|
goto remove_idr;
|
|
}
|
|
|
|
return buf;
|
|
|
|
remove_idr:
|
|
spin_lock(&mmg->lock);
|
|
idr_remove(&mmg->handles, buf->handle);
|
|
spin_unlock(&mmg->lock);
|
|
free_buf:
|
|
kfree(buf);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* hl_mmap_mem_buf_vm_close - handle mmap close
|
|
*
|
|
* @vma: the vma object for which mmap was closed.
|
|
*
|
|
* Put the memory buffer if it is no longer mapped.
|
|
*/
|
|
static void hl_mmap_mem_buf_vm_close(struct vm_area_struct *vma)
|
|
{
|
|
struct hl_mmap_mem_buf *buf =
|
|
(struct hl_mmap_mem_buf *)vma->vm_private_data;
|
|
long new_mmap_size;
|
|
|
|
new_mmap_size = buf->real_mapped_size - (vma->vm_end - vma->vm_start);
|
|
|
|
if (new_mmap_size > 0) {
|
|
buf->real_mapped_size = new_mmap_size;
|
|
return;
|
|
}
|
|
|
|
atomic_set(&buf->mmap, 0);
|
|
hl_mmap_mem_buf_put(buf);
|
|
vma->vm_private_data = NULL;
|
|
}
|
|
|
|
static const struct vm_operations_struct hl_mmap_mem_buf_vm_ops = {
|
|
.close = hl_mmap_mem_buf_vm_close
|
|
};
|
|
|
|
/**
|
|
* hl_mem_mgr_mmap - map the given buffer to the user
|
|
*
|
|
* @mmg: unifed memory manager
|
|
* @vma: the vma object for which mmap was closed.
|
|
* @args: additional args passed to behavior->mmap
|
|
*
|
|
* Map the buffer specified by the vma->vm_pgoff to the given vma.
|
|
*/
|
|
int hl_mem_mgr_mmap(struct hl_mem_mgr *mmg, struct vm_area_struct *vma,
|
|
void *args)
|
|
{
|
|
struct hl_mmap_mem_buf *buf;
|
|
u64 user_mem_size;
|
|
u64 handle;
|
|
int rc;
|
|
|
|
/* We use the page offset to hold the idr and thus we need to clear
|
|
* it before doing the mmap itself
|
|
*/
|
|
handle = vma->vm_pgoff << PAGE_SHIFT;
|
|
vma->vm_pgoff = 0;
|
|
|
|
/* Reference was taken here */
|
|
buf = hl_mmap_mem_buf_get(mmg, handle);
|
|
if (!buf) {
|
|
dev_err(mmg->dev,
|
|
"Memory mmap failed, no match to handle %llu\n", handle);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Validation check */
|
|
user_mem_size = vma->vm_end - vma->vm_start;
|
|
if (user_mem_size != ALIGN(buf->mappable_size, PAGE_SIZE)) {
|
|
dev_err(mmg->dev,
|
|
"Memory mmap failed, mmap VM size 0x%llx != 0x%llx allocated physical mem size\n",
|
|
user_mem_size, buf->mappable_size);
|
|
rc = -EINVAL;
|
|
goto put_mem;
|
|
}
|
|
|
|
#ifdef _HAS_TYPE_ARG_IN_ACCESS_OK
|
|
if (!access_ok(VERIFY_WRITE, (void __user *)(uintptr_t)vma->vm_start,
|
|
user_mem_size)) {
|
|
#else
|
|
if (!access_ok((void __user *)(uintptr_t)vma->vm_start,
|
|
user_mem_size)) {
|
|
#endif
|
|
dev_err(mmg->dev, "user pointer is invalid - 0x%lx\n",
|
|
vma->vm_start);
|
|
|
|
rc = -EINVAL;
|
|
goto put_mem;
|
|
}
|
|
|
|
if (atomic_cmpxchg(&buf->mmap, 0, 1)) {
|
|
dev_err(mmg->dev,
|
|
"Memory mmap failed, already mmaped to user\n");
|
|
rc = -EINVAL;
|
|
goto put_mem;
|
|
}
|
|
|
|
vma->vm_ops = &hl_mmap_mem_buf_vm_ops;
|
|
|
|
/* Note: We're transferring the memory reference to vma->vm_private_data here. */
|
|
|
|
vma->vm_private_data = buf;
|
|
|
|
rc = buf->behavior->mmap(buf, vma, args);
|
|
if (rc) {
|
|
atomic_set(&buf->mmap, 0);
|
|
goto put_mem;
|
|
}
|
|
|
|
buf->real_mapped_size = buf->mappable_size;
|
|
vma->vm_pgoff = handle;
|
|
|
|
return 0;
|
|
|
|
put_mem:
|
|
hl_mmap_mem_buf_put(buf);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* hl_mem_mgr_init - initialize unified memory manager
|
|
*
|
|
* @dev: owner device pointer
|
|
* @mmg: structure to initialize
|
|
*
|
|
* Initialize an instance of unified memory manager
|
|
*/
|
|
void hl_mem_mgr_init(struct device *dev, struct hl_mem_mgr *mmg)
|
|
{
|
|
mmg->dev = dev;
|
|
spin_lock_init(&mmg->lock);
|
|
idr_init(&mmg->handles);
|
|
}
|
|
|
|
/**
|
|
* hl_mem_mgr_fini - release unified memory manager
|
|
*
|
|
* @mmg: parent unifed memory manager
|
|
*
|
|
* Release the unified memory manager. Shall be called from an interrupt context.
|
|
*/
|
|
void hl_mem_mgr_fini(struct hl_mem_mgr *mmg)
|
|
{
|
|
struct hl_mmap_mem_buf *buf;
|
|
struct idr *idp;
|
|
u32 id;
|
|
|
|
idp = &mmg->handles;
|
|
|
|
idr_for_each_entry(idp, buf, id) {
|
|
if (hl_mmap_mem_buf_put(buf) != 1)
|
|
dev_err(mmg->dev,
|
|
"Buff handle %u for CTX is still alive\n", id);
|
|
}
|
|
|
|
/* TODO: can it happen that some buffer is still in use at this point? */
|
|
|
|
idr_destroy(&mmg->handles);
|
|
}
|