2026-01-29 22:25:33 +08:00

524 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 - 2023, Shanghai Yunsilicon Technology Co., Ltd.
* All rights reserved.
*/
#include "ib_peer_mem.h"
#include <rdma/ib_verbs.h>
#include "ib_umem_ex.h"
static DEFINE_MUTEX(peer_memory_mutex);
static LIST_HEAD(peer_memory_list);
static struct kobject *peers_kobj;
static void complete_peer(struct kref *kref);
static struct ib_peer_memory_client *get_peer_by_kobj(void *kobj);
static ssize_t version_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%s\n", ib_peer_client->peer_mem->version);
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_alloc_mrs_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%llu\n", (u64)atomic64_read(&ib_peer_client->stats.num_alloc_mrs));
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_dealloc_mrs_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%llu\n", (u64)atomic64_read(&ib_peer_client->stats.num_dealloc_mrs));
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_reg_pages_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%llu\n", (u64)atomic64_read(&ib_peer_client->stats.num_reg_pages));
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_dereg_pages_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%llu\n", (u64)atomic64_read(&ib_peer_client->stats.num_dereg_pages));
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_reg_bytes_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%llu\n", (u64)atomic64_read(&ib_peer_client->stats.num_reg_bytes));
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_dereg_bytes_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%llu\n", (u64)atomic64_read(&ib_peer_client->stats.num_dereg_bytes));
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static ssize_t num_free_callbacks_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ib_peer_memory_client *ib_peer_client = get_peer_by_kobj(kobj);
if (ib_peer_client) {
sprintf(buf, "%lu\n", ib_peer_client->stats.num_free_callbacks);
kref_put(&ib_peer_client->ref, complete_peer);
return strlen(buf);
}
/* not found - nothing is return */
return 0;
}
static struct kobj_attribute version_attr = __ATTR_RO(version);
static struct kobj_attribute num_alloc_mrs = __ATTR_RO(num_alloc_mrs);
static struct kobj_attribute num_dealloc_mrs = __ATTR_RO(num_dealloc_mrs);
static struct kobj_attribute num_reg_pages = __ATTR_RO(num_reg_pages);
static struct kobj_attribute num_dereg_pages = __ATTR_RO(num_dereg_pages);
static struct kobj_attribute num_reg_bytes = __ATTR_RO(num_reg_bytes);
static struct kobj_attribute num_dereg_bytes = __ATTR_RO(num_dereg_bytes);
static struct kobj_attribute num_free_callbacks = __ATTR_RO(num_free_callbacks);
static struct attribute *peer_mem_attrs[] = {
&version_attr.attr,
&num_alloc_mrs.attr,
&num_dealloc_mrs.attr,
&num_reg_pages.attr,
&num_dereg_pages.attr,
&num_reg_bytes.attr,
&num_dereg_bytes.attr,
&num_free_callbacks.attr,
NULL,
};
static void destroy_peer_sysfs(struct ib_peer_memory_client *ib_peer_client)
{
kobject_put(ib_peer_client->kobj);
if (list_empty(&peer_memory_list))
kobject_put(peers_kobj);
}
static int create_peer_sysfs(struct ib_peer_memory_client *ib_peer_client)
{
int ret;
if (list_empty(&peer_memory_list)) {
/* creating under /sys/kernel/mm */
peers_kobj = kobject_create_and_add("memory_peers", mm_kobj);
if (!peers_kobj)
return -ENOMEM;
}
ib_peer_client->peer_mem_attr_group.attrs = peer_mem_attrs;
/* Dir alreday was created explicitly to get its kernel object for further usage */
ib_peer_client->peer_mem_attr_group.name = NULL;
ib_peer_client->kobj = kobject_create_and_add(ib_peer_client->peer_mem->name,
peers_kobj);
if (!ib_peer_client->kobj) {
ret = -EINVAL;
goto free;
}
/* Create the files associated with this kobject */
ret = sysfs_create_group(ib_peer_client->kobj,
&ib_peer_client->peer_mem_attr_group);
if (ret)
goto peer_free;
return 0;
peer_free:
kobject_put(ib_peer_client->kobj);
free:
if (list_empty(&peer_memory_list))
kobject_put(peers_kobj);
return ret;
}
static struct ib_peer_memory_client *get_peer_by_kobj(void *kobj)
{
struct ib_peer_memory_client *ib_peer_client;
mutex_lock(&peer_memory_mutex);
list_for_each_entry(ib_peer_client, &peer_memory_list, core_peer_list) {
if (ib_peer_client->kobj == kobj) {
kref_get(&ib_peer_client->ref);
goto found;
}
}
ib_peer_client = NULL;
found:
mutex_unlock(&peer_memory_mutex);
return ib_peer_client;
}
/* Caller should be holding the peer client lock, ib_peer_client->lock */
static struct core_ticket *ib_peer_search_context(struct ib_peer_memory_client *ib_peer_client,
u64 key)
{
struct core_ticket *core_ticket;
list_for_each_entry(core_ticket, &ib_peer_client->core_ticket_list,
ticket_list) {
if (core_ticket->key == key)
return core_ticket;
}
return NULL;
}
static int ib_invalidate_peer_memory(void *reg_handle, u64 core_context)
{
struct ib_peer_memory_client *ib_peer_client = reg_handle;
struct invalidation_ctx *invalidation_ctx;
struct core_ticket *core_ticket;
int need_unlock = 1;
mutex_lock(&ib_peer_client->lock);
ib_peer_client->stats.num_free_callbacks += 1;
core_ticket = ib_peer_search_context(ib_peer_client, core_context);
if (!core_ticket)
goto out;
invalidation_ctx = (struct invalidation_ctx *)core_ticket->context;
/* If context is not ready yet, mark it to be invalidated */
if (!invalidation_ctx->func) {
invalidation_ctx->peer_invalidated = 1;
goto out;
}
invalidation_ctx->func(invalidation_ctx->cookie,
invalidation_ctx->umem_ex, 0, 0);
if (invalidation_ctx->inflight_invalidation) {
/* init the completion to wait on before letting other thread to run */
init_completion(&invalidation_ctx->comp);
mutex_unlock(&ib_peer_client->lock);
need_unlock = 0;
wait_for_completion(&invalidation_ctx->comp);
}
kfree(invalidation_ctx);
out:
if (need_unlock)
mutex_unlock(&ib_peer_client->lock);
return 0;
}
static int ib_peer_insert_context(struct ib_peer_memory_client *ib_peer_client,
void *context,
u64 *context_ticket)
{
struct core_ticket *core_ticket = kzalloc(sizeof(*core_ticket), GFP_KERNEL);
if (!core_ticket)
return -ENOMEM;
mutex_lock(&ib_peer_client->lock);
core_ticket->key = ib_peer_client->last_ticket++;
core_ticket->context = context;
list_add_tail(&core_ticket->ticket_list,
&ib_peer_client->core_ticket_list);
*context_ticket = core_ticket->key;
mutex_unlock(&ib_peer_client->lock);
return 0;
}
/*
* Caller should be holding the peer client lock, specifically,
* the caller should hold ib_peer_client->lock
*/
static int ib_peer_remove_context(struct ib_peer_memory_client *ib_peer_client,
u64 key)
{
struct core_ticket *core_ticket;
list_for_each_entry(core_ticket, &ib_peer_client->core_ticket_list,
ticket_list) {
if (core_ticket->key == key) {
list_del(&core_ticket->ticket_list);
kfree(core_ticket);
return 0;
}
}
return 1;
}
/*
* ib_peer_create_invalidation_ctx - creates invalidation context for a given umem
* @ib_peer_mem: peer client to be used
* @umem: umem struct belongs to that context
* @invalidation_ctx: output context
*/
int ib_peer_create_invalidation_ctx(struct ib_peer_memory_client *ib_peer_mem,
struct ib_umem_ex *umem_ex,
struct invalidation_ctx **invalidation_ctx)
{
int ret;
struct invalidation_ctx *ctx;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ret = ib_peer_insert_context(ib_peer_mem, ctx,
&ctx->context_ticket);
if (ret) {
kfree(ctx);
return ret;
}
ctx->umem_ex = umem_ex;
umem_ex->invalidation_ctx = ctx;
*invalidation_ctx = ctx;
return 0;
}
/**
* ** ib_peer_destroy_invalidation_ctx - destroy a given invalidation context
* ** @ib_peer_mem: peer client to be used
* ** @invalidation_ctx: context to be invalidated
* **/
void ib_peer_destroy_invalidation_ctx(struct ib_peer_memory_client *ib_peer_mem,
struct invalidation_ctx *invalidation_ctx)
{
int peer_callback;
int inflight_invalidation;
/* If we are under peer callback lock was already taken.*/
if (!invalidation_ctx->peer_callback)
mutex_lock(&ib_peer_mem->lock);
ib_peer_remove_context(ib_peer_mem, invalidation_ctx->context_ticket);
/* make sure to check inflight flag after took the lock and remove from tree.
* in addition, from that point using local variables for peer_callback and
* inflight_invalidation as after the complete invalidation_ctx can't be accessed
* any more as it may be freed by the callback.
*/
peer_callback = invalidation_ctx->peer_callback;
inflight_invalidation = invalidation_ctx->inflight_invalidation;
if (inflight_invalidation)
complete(&invalidation_ctx->comp);
/* On peer callback lock is handled externally */
if (!peer_callback)
mutex_unlock(&ib_peer_mem->lock);
/* in case under callback context or callback is pending
* let it free the invalidation context
*/
if (!peer_callback && !inflight_invalidation)
kfree(invalidation_ctx);
}
static int ib_memory_peer_check_mandatory(const struct peer_memory_client
*peer_client)
{
#define PEER_MEM_MANDATORY_FUNC(x) { offsetof(struct peer_memory_client, x), #x }
static const struct {
size_t offset;
char *name;
} mandatory_table[] = {
PEER_MEM_MANDATORY_FUNC(acquire),
PEER_MEM_MANDATORY_FUNC(get_pages),
PEER_MEM_MANDATORY_FUNC(put_pages),
PEER_MEM_MANDATORY_FUNC(get_page_size),
PEER_MEM_MANDATORY_FUNC(dma_map),
PEER_MEM_MANDATORY_FUNC(dma_unmap)
};
int i;
for (i = 0; i < ARRAY_SIZE(mandatory_table); ++i) {
if (!*(void **)((void *)peer_client + mandatory_table[i].offset)) {
pr_err("Peer memory %s is missing mandatory function %s\n",
peer_client->name, mandatory_table[i].name);
return -EINVAL;
}
}
return 0;
}
static void complete_peer(struct kref *kref)
{
struct ib_peer_memory_client *ib_peer_client =
container_of(kref, struct ib_peer_memory_client, ref);
complete(&ib_peer_client->unload_comp);
}
void *ib_register_peer_memory_client(const struct peer_memory_client *peer_client,
invalidate_peer_memory *invalidate_callback)
{
struct ib_peer_memory_client *ib_peer_client;
if (ib_memory_peer_check_mandatory(peer_client))
return NULL;
ib_peer_client = kzalloc(sizeof(*ib_peer_client), GFP_KERNEL);
if (!ib_peer_client)
return NULL;
INIT_LIST_HEAD(&ib_peer_client->core_ticket_list);
mutex_init(&ib_peer_client->lock);
init_completion(&ib_peer_client->unload_comp);
kref_init(&ib_peer_client->ref);
ib_peer_client->peer_mem = peer_client;
/* Once peer supplied a non NULL callback it's an indication that
* invalidation support is required for any memory owning.
*/
if (invalidate_callback) {
*invalidate_callback = ib_invalidate_peer_memory;
ib_peer_client->invalidation_required = 1;
}
ib_peer_client->last_ticket = 1;
mutex_lock(&peer_memory_mutex);
if (create_peer_sysfs(ib_peer_client)) {
kfree(ib_peer_client);
ib_peer_client = NULL;
goto end;
}
list_add_tail(&ib_peer_client->core_peer_list, &peer_memory_list);
end:
mutex_unlock(&peer_memory_mutex);
return ib_peer_client;
}
EXPORT_SYMBOL(ib_register_peer_memory_client);
void ib_unregister_peer_memory_client(void *reg_handle)
{
struct ib_peer_memory_client *ib_peer_client = reg_handle;
mutex_lock(&peer_memory_mutex);
list_del(&ib_peer_client->core_peer_list);
destroy_peer_sysfs(ib_peer_client);
mutex_unlock(&peer_memory_mutex);
kref_put(&ib_peer_client->ref, complete_peer);
wait_for_completion(&ib_peer_client->unload_comp);
kfree(ib_peer_client);
}
EXPORT_SYMBOL(ib_unregister_peer_memory_client);
struct ib_peer_memory_client *ib_get_peer_client(struct ib_ucontext *context, unsigned long addr,
size_t size, unsigned long peer_mem_flags,
void **peer_client_context)
{
struct ib_peer_memory_client *ib_peer_client = NULL;
int ret = 0;
mutex_lock(&peer_memory_mutex);
list_for_each_entry(ib_peer_client, &peer_memory_list, core_peer_list) {
/* In case peer requires invalidation it can't own
* memory which doesn't support it
*/
if ((ib_peer_client->invalidation_required &&
(!(peer_mem_flags & IB_PEER_MEM_INVAL_SUPP))))
continue;
ret = ib_peer_client->peer_mem->acquire(addr, size, NULL, NULL,
peer_client_context);
if (ret > 0)
goto found;
}
ib_peer_client = NULL;
found:
if (ib_peer_client)
kref_get(&ib_peer_client->ref);
mutex_unlock(&peer_memory_mutex);
return ib_peer_client;
}
EXPORT_SYMBOL(ib_get_peer_client);
void ib_put_peer_client(struct ib_peer_memory_client *ib_peer_client,
void *peer_client_context)
{
if (ib_peer_client->peer_mem->release)
ib_peer_client->peer_mem->release(peer_client_context);
kref_put(&ib_peer_client->ref, complete_peer);
}
EXPORT_SYMBOL(ib_put_peer_client);
int ib_get_peer_private_data(struct ib_ucontext *context, u64 peer_id,
char *peer_name)
{
pr_warn("predefine peer mem is not supported by now");
return -1;
}
EXPORT_SYMBOL(ib_get_peer_private_data);
void ib_put_peer_private_data(struct ib_ucontext *context)
{
pr_warn("predefine peer mem is not supported by now");
}
EXPORT_SYMBOL(ib_put_peer_private_data);