root/drivers/dibs/dibs_main.c
// SPDX-License-Identifier: GPL-2.0
/*
 *  DIBS - Direct Internal Buffer Sharing
 *
 *  Implementation of the DIBS class module
 *
 *  Copyright IBM Corp. 2025
 */
#define pr_fmt(fmt) "dibs: " fmt

#include <linux/module.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/dibs.h>

#include "dibs_loopback.h"

MODULE_DESCRIPTION("Direct Internal Buffer Sharing class");
MODULE_LICENSE("GPL");

static const struct class dibs_class = {
        .name           = "dibs",
};

/* use an array rather a list for fast mapping: */
static struct dibs_client *clients[MAX_DIBS_CLIENTS];
static u8 max_client;
static DEFINE_MUTEX(clients_lock);
struct dibs_dev_list {
        struct list_head list;
        struct mutex mutex; /* protects dibs device list */
};

static struct dibs_dev_list dibs_dev_list = {
        .list = LIST_HEAD_INIT(dibs_dev_list.list),
        .mutex = __MUTEX_INITIALIZER(dibs_dev_list.mutex),
};

static void dibs_setup_forwarding(struct dibs_client *client,
                                  struct dibs_dev *dibs)
{
        unsigned long flags;

        spin_lock_irqsave(&dibs->lock, flags);
        dibs->subs[client->id] = client;
        spin_unlock_irqrestore(&dibs->lock, flags);
}

int dibs_register_client(struct dibs_client *client)
{
        struct dibs_dev *dibs;
        int i, rc = -ENOSPC;

        mutex_lock(&dibs_dev_list.mutex);
        mutex_lock(&clients_lock);
        for (i = 0; i < MAX_DIBS_CLIENTS; ++i) {
                if (!clients[i]) {
                        clients[i] = client;
                        client->id = i;
                        if (i == max_client)
                                max_client++;
                        rc = 0;
                        break;
                }
        }
        mutex_unlock(&clients_lock);

        if (i < MAX_DIBS_CLIENTS) {
                /* initialize with all devices that we got so far */
                list_for_each_entry(dibs, &dibs_dev_list.list, list) {
                        dibs->priv[i] = NULL;
                        client->ops->add_dev(dibs);
                        dibs_setup_forwarding(client, dibs);
                }
        }
        mutex_unlock(&dibs_dev_list.mutex);

        return rc;
}
EXPORT_SYMBOL_GPL(dibs_register_client);

int dibs_unregister_client(struct dibs_client *client)
{
        struct dibs_dev *dibs;
        unsigned long flags;
        int max_dmbs;
        int rc = 0;

        mutex_lock(&dibs_dev_list.mutex);
        list_for_each_entry(dibs, &dibs_dev_list.list, list) {
                spin_lock_irqsave(&dibs->lock, flags);
                max_dmbs = dibs->ops->max_dmbs();
                for (int i = 0; i < max_dmbs; ++i) {
                        if (dibs->dmb_clientid_arr[i] == client->id) {
                                WARN(1, "%s: attempt to unregister '%s' with registered dmb(s)\n",
                                     __func__, client->name);
                                rc = -EBUSY;
                                goto err_reg_dmb;
                        }
                }
                /* Stop forwarding IRQs and events */
                dibs->subs[client->id] = NULL;
                spin_unlock_irqrestore(&dibs->lock, flags);
                clients[client->id]->ops->del_dev(dibs);
                dibs->priv[client->id] = NULL;
        }

        mutex_lock(&clients_lock);
        clients[client->id] = NULL;
        if (client->id + 1 == max_client)
                max_client--;
        mutex_unlock(&clients_lock);

        mutex_unlock(&dibs_dev_list.mutex);
        return rc;

err_reg_dmb:
        spin_unlock_irqrestore(&dibs->lock, flags);
        mutex_unlock(&dibs_dev_list.mutex);
        return rc;
}
EXPORT_SYMBOL_GPL(dibs_unregister_client);

static void dibs_dev_release(struct device *dev)
{
        struct dibs_dev *dibs;

        dibs = container_of(dev, struct dibs_dev, dev);

        kfree(dibs);
}

struct dibs_dev *dibs_dev_alloc(void)
{
        struct dibs_dev *dibs;

        dibs = kzalloc_obj(*dibs);
        if (!dibs)
                return dibs;
        dibs->dev.release = dibs_dev_release;
        dibs->dev.class = &dibs_class;
        device_initialize(&dibs->dev);

        return dibs;
}
EXPORT_SYMBOL_GPL(dibs_dev_alloc);

static ssize_t gid_show(struct device *dev, struct device_attribute *attr,
                        char *buf)
{
        struct dibs_dev *dibs;

        dibs = container_of(dev, struct dibs_dev, dev);

        return sysfs_emit(buf, "%pUb\n", &dibs->gid);
}
static DEVICE_ATTR_RO(gid);

static ssize_t fabric_id_show(struct device *dev, struct device_attribute *attr,
                              char *buf)
{
        struct dibs_dev *dibs;
        u16 fabric_id;

        dibs = container_of(dev, struct dibs_dev, dev);
        fabric_id = dibs->ops->get_fabric_id(dibs);

        return sysfs_emit(buf, "0x%04x\n", fabric_id);
}
static DEVICE_ATTR_RO(fabric_id);

static struct attribute *dibs_dev_attrs[] = {
        &dev_attr_gid.attr,
        &dev_attr_fabric_id.attr,
        NULL,
};

static const struct attribute_group dibs_dev_attr_group = {
        .attrs = dibs_dev_attrs,
};

int dibs_dev_add(struct dibs_dev *dibs)
{
        int max_dmbs;
        int i, ret;

        max_dmbs = dibs->ops->max_dmbs();
        spin_lock_init(&dibs->lock);
        dibs->dmb_clientid_arr = kzalloc(max_dmbs, GFP_KERNEL);
        if (!dibs->dmb_clientid_arr)
                return -ENOMEM;
        memset(dibs->dmb_clientid_arr, NO_DIBS_CLIENT, max_dmbs);

        ret = device_add(&dibs->dev);
        if (ret)
                goto free_client_arr;

        ret = sysfs_create_group(&dibs->dev.kobj, &dibs_dev_attr_group);
        if (ret) {
                dev_err(&dibs->dev, "sysfs_create_group failed for dibs_dev\n");
                goto err_device_del;
        }
        mutex_lock(&dibs_dev_list.mutex);
        mutex_lock(&clients_lock);
        for (i = 0; i < max_client; ++i) {
                if (clients[i]) {
                        clients[i]->ops->add_dev(dibs);
                        dibs_setup_forwarding(clients[i], dibs);
                }
        }
        mutex_unlock(&clients_lock);
        list_add(&dibs->list, &dibs_dev_list.list);
        mutex_unlock(&dibs_dev_list.mutex);

        return 0;

err_device_del:
        device_del(&dibs->dev);
free_client_arr:
        kfree(dibs->dmb_clientid_arr);
        return ret;

}
EXPORT_SYMBOL_GPL(dibs_dev_add);

void dibs_dev_del(struct dibs_dev *dibs)
{
        unsigned long flags;
        int i;

        sysfs_remove_group(&dibs->dev.kobj, &dibs_dev_attr_group);

        spin_lock_irqsave(&dibs->lock, flags);
        for (i = 0; i < MAX_DIBS_CLIENTS; ++i)
                dibs->subs[i] = NULL;
        spin_unlock_irqrestore(&dibs->lock, flags);

        mutex_lock(&dibs_dev_list.mutex);
        mutex_lock(&clients_lock);
        for (i = 0; i < max_client; ++i) {
                if (clients[i])
                        clients[i]->ops->del_dev(dibs);
        }
        mutex_unlock(&clients_lock);
        list_del_init(&dibs->list);
        mutex_unlock(&dibs_dev_list.mutex);

        device_del(&dibs->dev);
        kfree(dibs->dmb_clientid_arr);
}
EXPORT_SYMBOL_GPL(dibs_dev_del);

static int __init dibs_init(void)
{
        int rc;

        rc = class_register(&dibs_class);
        if (rc)
                return rc;

        rc = dibs_loopback_init();
        if (rc)
                pr_err("%s fails with %d\n", __func__, rc);

        return rc;
}

static void __exit dibs_exit(void)
{
        dibs_loopback_exit();
        class_unregister(&dibs_class);
}

subsys_initcall(dibs_init);
module_exit(dibs_exit);