| // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
| /* Copyright (c) 2018 Mellanox Technologies */ |
| |
| #include <linux/jhash.h> |
| #include <linux/slab.h> |
| #include <linux/xarray.h> |
| #include <linux/hashtable.h> |
| |
| #include "mapping.h" |
| |
| #define MAPPING_GRACE_PERIOD 2000 |
| |
| struct mapping_ctx { |
| struct xarray xarray; |
| DECLARE_HASHTABLE(ht, 8); |
| struct mutex lock; /* Guards hashtable and xarray */ |
| unsigned long max_id; |
| size_t data_size; |
| bool delayed_removal; |
| struct delayed_work dwork; |
| struct list_head pending_list; |
| spinlock_t pending_list_lock; /* Guards pending list */ |
| }; |
| |
| struct mapping_item { |
| struct rcu_head rcu; |
| struct list_head list; |
| unsigned long timeout; |
| struct hlist_node node; |
| int cnt; |
| u32 id; |
| char data[]; |
| }; |
| |
| int mapping_add(struct mapping_ctx *ctx, void *data, u32 *id) |
| { |
| struct mapping_item *mi; |
| int err = -ENOMEM; |
| u32 hash_key; |
| |
| mutex_lock(&ctx->lock); |
| |
| hash_key = jhash(data, ctx->data_size, 0); |
| hash_for_each_possible(ctx->ht, mi, node, hash_key) { |
| if (!memcmp(data, mi->data, ctx->data_size)) |
| goto attach; |
| } |
| |
| mi = kzalloc(sizeof(*mi) + ctx->data_size, GFP_KERNEL); |
| if (!mi) |
| goto err_alloc; |
| |
| memcpy(mi->data, data, ctx->data_size); |
| hash_add(ctx->ht, &mi->node, hash_key); |
| |
| err = xa_alloc(&ctx->xarray, &mi->id, mi, XA_LIMIT(1, ctx->max_id), |
| GFP_KERNEL); |
| if (err) |
| goto err_assign; |
| attach: |
| ++mi->cnt; |
| *id = mi->id; |
| |
| mutex_unlock(&ctx->lock); |
| |
| return 0; |
| |
| err_assign: |
| hash_del(&mi->node); |
| kfree(mi); |
| err_alloc: |
| mutex_unlock(&ctx->lock); |
| |
| return err; |
| } |
| |
| static void mapping_remove_and_free(struct mapping_ctx *ctx, |
| struct mapping_item *mi) |
| { |
| xa_erase(&ctx->xarray, mi->id); |
| kfree_rcu(mi, rcu); |
| } |
| |
| static void mapping_free_item(struct mapping_ctx *ctx, |
| struct mapping_item *mi) |
| { |
| if (!ctx->delayed_removal) { |
| mapping_remove_and_free(ctx, mi); |
| return; |
| } |
| |
| mi->timeout = jiffies + msecs_to_jiffies(MAPPING_GRACE_PERIOD); |
| |
| spin_lock(&ctx->pending_list_lock); |
| list_add_tail(&mi->list, &ctx->pending_list); |
| spin_unlock(&ctx->pending_list_lock); |
| |
| schedule_delayed_work(&ctx->dwork, MAPPING_GRACE_PERIOD); |
| } |
| |
| int mapping_remove(struct mapping_ctx *ctx, u32 id) |
| { |
| unsigned long index = id; |
| struct mapping_item *mi; |
| int err = -ENOENT; |
| |
| mutex_lock(&ctx->lock); |
| mi = xa_load(&ctx->xarray, index); |
| if (!mi) |
| goto out; |
| err = 0; |
| |
| if (--mi->cnt > 0) |
| goto out; |
| |
| hash_del(&mi->node); |
| mapping_free_item(ctx, mi); |
| out: |
| mutex_unlock(&ctx->lock); |
| |
| return err; |
| } |
| |
| int mapping_find(struct mapping_ctx *ctx, u32 id, void *data) |
| { |
| unsigned long index = id; |
| struct mapping_item *mi; |
| int err = -ENOENT; |
| |
| rcu_read_lock(); |
| mi = xa_load(&ctx->xarray, index); |
| if (!mi) |
| goto err_find; |
| |
| memcpy(data, mi->data, ctx->data_size); |
| err = 0; |
| |
| err_find: |
| rcu_read_unlock(); |
| return err; |
| } |
| |
| static void |
| mapping_remove_and_free_list(struct mapping_ctx *ctx, struct list_head *list) |
| { |
| struct mapping_item *mi; |
| |
| list_for_each_entry(mi, list, list) |
| mapping_remove_and_free(ctx, mi); |
| } |
| |
| static void mapping_work_handler(struct work_struct *work) |
| { |
| unsigned long min_timeout = 0, now = jiffies; |
| struct mapping_item *mi, *next; |
| LIST_HEAD(pending_items); |
| struct mapping_ctx *ctx; |
| |
| ctx = container_of(work, struct mapping_ctx, dwork.work); |
| |
| spin_lock(&ctx->pending_list_lock); |
| list_for_each_entry_safe(mi, next, &ctx->pending_list, list) { |
| if (time_after(now, mi->timeout)) |
| list_move(&mi->list, &pending_items); |
| else if (!min_timeout || |
| time_before(mi->timeout, min_timeout)) |
| min_timeout = mi->timeout; |
| } |
| spin_unlock(&ctx->pending_list_lock); |
| |
| mapping_remove_and_free_list(ctx, &pending_items); |
| |
| if (min_timeout) |
| schedule_delayed_work(&ctx->dwork, abs(min_timeout - now)); |
| } |
| |
| static void mapping_flush_work(struct mapping_ctx *ctx) |
| { |
| if (!ctx->delayed_removal) |
| return; |
| |
| cancel_delayed_work_sync(&ctx->dwork); |
| mapping_remove_and_free_list(ctx, &ctx->pending_list); |
| } |
| |
| struct mapping_ctx * |
| mapping_create(size_t data_size, u32 max_id, bool delayed_removal) |
| { |
| struct mapping_ctx *ctx; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return ERR_PTR(-ENOMEM); |
| |
| ctx->max_id = max_id ? max_id : UINT_MAX; |
| ctx->data_size = data_size; |
| |
| if (delayed_removal) { |
| INIT_DELAYED_WORK(&ctx->dwork, mapping_work_handler); |
| INIT_LIST_HEAD(&ctx->pending_list); |
| spin_lock_init(&ctx->pending_list_lock); |
| ctx->delayed_removal = true; |
| } |
| |
| mutex_init(&ctx->lock); |
| xa_init_flags(&ctx->xarray, XA_FLAGS_ALLOC1); |
| |
| return ctx; |
| } |
| |
| void mapping_destroy(struct mapping_ctx *ctx) |
| { |
| mapping_flush_work(ctx); |
| xa_destroy(&ctx->xarray); |
| mutex_destroy(&ctx->lock); |
| |
| kfree(ctx); |
| } |