// 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 unified memory manager
* @ handle : requested buffer handle
*
* 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_dbg(mmg->dev, "Buff get failed, no match to handle %#llx\n" , handle);
return NULL;
}
kref_get(&buf->refcount);
spin_unlock(&mmg->lock);
return buf;
}
/**
* hl_mmap_mem_buf_destroy - destroy the unused buffer
*
* @ buf : memory manager buffer descriptor
*
* Internal function , used as a final step of buffer release . Shall be invoked
* only when the buffer is no longer in use ( removed from idr ) . Will call the
* release callback ( if applicable ) , and free the memory .
*/
static void hl_mmap_mem_buf_destroy(struct hl_mmap_mem_buf *buf)
{
if (buf->behavior->release)
buf->behavior->release(buf);
kfree(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);
hl_mmap_mem_buf_destroy(buf);
}
/**
* hl_mmap_mem_buf_remove_idr_locked - remove handle from idr
*
* @ kref : kref that reached 0 .
*
* Internal function , used for kref put by handle . Assumes mmg lock is taken .
* Will remove the buffer from idr , without destroying it .
*/
static void hl_mmap_mem_buf_remove_idr_locked(struct kref *kref)
{
struct hl_mmap_mem_buf *buf =
container_of(kref, struct hl_mmap_mem_buf, refcount);
idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
}
/**
* 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_put_handle - decrease the reference to the buffer with the
* given handle .
*
* @ mmg : parent unified memory manager
* @ handle : requested buffer handle
*
* Decrease the reference to the buffer , and release it if it was the last one .
* Shall not be called from an interrupt context . Return - EINVAL if handle was
* not found , else return the put outcome ( 0 or 1 ) .
*/
int hl_mmap_mem_buf_put_handle(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_dbg(mmg->dev,
"Buff put failed, no match to handle %#llx\n" , handle);
return -EINVAL;
}
if (kref_put(&buf->refcount, hl_mmap_mem_buf_remove_idr_locked)) {
spin_unlock(&mmg->lock);
hl_mmap_mem_buf_destroy(buf);
return 1 ;
}
spin_unlock(&mmg->lock);
return 0 ;
}
/**
* hl_mmap_mem_buf_alloc - allocate a new mappable buffer
*
* @ mmg : parent unified 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,
"%s: Failed to allocate IDR for a new buffer, rc=%d\n" ,
behavior->topic, 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, "%s: Failure in buffer alloc callback %d\n" ,
behavior->topic, rc);
goto remove_idr;
}
return buf;
remove_idr:
spin_lock(&mmg->lock);
idr_remove(&mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
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 : unified 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 %#llx\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,
"%s: Memory mmap failed, mmap VM size 0x%llx != 0x%llx allocated physical mem size\n" ,
buf->behavior->topic, 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, "%s: User pointer is invalid - 0x%lx\n" ,
buf->behavior->topic, vma->vm_start);
rc = -EINVAL;
goto put_mem;
}
if (atomic_cmpxchg(&buf->mmap, 0 , 1 )) {
dev_err(mmg->dev,
"%s, Memory mmap failed, already mapped to user\n" ,
buf->behavior->topic);
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 >> PAGE_SHIFT;
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);
}
static void hl_mem_mgr_fini_stats_reset(struct hl_mem_mgr_fini_stats *stats)
{
if (!stats)
return ;
memset(stats, 0 , sizeof (*stats));
}
static void hl_mem_mgr_fini_stats_inc(u64 mem_id, struct hl_mem_mgr_fini_stats *stats)
{
if (!stats)
return ;
switch (mem_id) {
case HL_MMAP_TYPE_CB:
++stats->n_busy_cb;
break ;
case HL_MMAP_TYPE_TS_BUFF:
++stats->n_busy_ts;
break ;
default :
/* we currently store only CB/TS so this shouldn't happen */
++stats->n_busy_other;
}
}
/**
* hl_mem_mgr_fini - release unified memory manager
*
* @ mmg : parent unified memory manager
* @ stats : if non - NULL , will return some counters for handles that could not be removed .
*
* Release the unified memory manager . Shall be called from an interrupt context .
*/
void hl_mem_mgr_fini(struct hl_mem_mgr *mmg, struct hl_mem_mgr_fini_stats *stats)
{
struct hl_mmap_mem_buf *buf;
struct idr *idp;
const char *topic;
u64 mem_id;
u32 id;
hl_mem_mgr_fini_stats_reset(stats);
idp = &mmg->handles;
idr_for_each_entry(idp, buf, id) {
topic = buf->behavior->topic;
mem_id = buf->behavior->mem_id;
if (hl_mmap_mem_buf_put(buf) != 1 ) {
dev_err(mmg->dev,
"%s: Buff handle %u for CTX is still alive\n" ,
topic, id);
hl_mem_mgr_fini_stats_inc(mem_id, stats);
}
}
}
/**
* hl_mem_mgr_idr_destroy ( ) - destroy memory manager IDR .
* @ mmg : parent unified memory manager
*
* Destroy the memory manager IDR .
* Shall be called when IDR is empty and no memory buffers are in use .
*/
void hl_mem_mgr_idr_destroy(struct hl_mem_mgr *mmg)
{
if (!idr_is_empty(&mmg->handles))
dev_crit(mmg->dev, "memory manager IDR is destroyed while it is not empty!\n" );
idr_destroy(&mmg->handles);
}
Messung V0.5 in Prozent C=94 H=93 G=93
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-10-12)
¤
*© Formatika GbR, Deutschland