279 lines
6.1 KiB
C
279 lines
6.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* DIBS - Direct Internal Buffer Sharing
|
|
*
|
|
* Implementation of the DIBS class module
|
|
*
|
|
* Copyright IBM Corp. 2025
|
|
*/
|
|
#define KMSG_COMPONENT "dibs"
|
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " 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 struct class *dibs_class;
|
|
|
|
/* 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(sizeof(*dibs), GFP_KERNEL);
|
|
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;
|
|
|
|
memset(clients, 0, sizeof(clients));
|
|
max_client = 0;
|
|
|
|
dibs_class = class_create("dibs");
|
|
if (IS_ERR(dibs_class))
|
|
return PTR_ERR(dibs_class);
|
|
|
|
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_destroy(dibs_class);
|
|
}
|
|
|
|
module_init(dibs_init);
|
|
module_exit(dibs_exit);
|