Commit d3d912eb authored by Ashutosh Dixit's avatar Ashutosh Dixit Committed by Greg Kroah-Hartman

misc: mic: Add support for kernel mode SCIF clients

Add support for registration/de-registration of kernel mode SCIF
clients. SCIF clients are probed with new and existing SCIF peer
devices. Similarly the client remove method is called when SCIF
peer devices are removed.

Changes to SCIF peer device framework necessitated by supporting
kernel mode SCIF clients are also included in this patch.
Reviewed-by: default avatarNikhil Rao <nikhil.rao@intel.com>
Reviewed-by: default avatarSudeep Dutt <sudeep.dutt@intel.com>
Signed-off-by: default avatarAshutosh Dixit <ashutosh.dixit@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent b7f94441
......@@ -1430,3 +1430,46 @@ int scif_get_node_ids(u16 *nodes, int len, u16 *self)
return online;
}
EXPORT_SYMBOL_GPL(scif_get_node_ids);
static int scif_add_client_dev(struct device *dev, struct subsys_interface *si)
{
struct scif_client *client =
container_of(si, struct scif_client, si);
struct scif_peer_dev *spdev =
container_of(dev, struct scif_peer_dev, dev);
if (client->probe)
client->probe(spdev);
return 0;
}
static void scif_remove_client_dev(struct device *dev,
struct subsys_interface *si)
{
struct scif_client *client =
container_of(si, struct scif_client, si);
struct scif_peer_dev *spdev =
container_of(dev, struct scif_peer_dev, dev);
if (client->remove)
client->remove(spdev);
}
void scif_client_unregister(struct scif_client *client)
{
subsys_interface_unregister(&client->si);
}
EXPORT_SYMBOL_GPL(scif_client_unregister);
int scif_client_register(struct scif_client *client)
{
struct subsys_interface *si = &client->si;
si->name = client->name;
si->subsys = &scif_peer_bus;
si->add_dev = scif_add_client_dev;
si->remove_dev = scif_remove_client_dev;
return subsys_interface_register(&client->si);
}
EXPORT_SYMBOL_GPL(scif_client_register);
......@@ -80,35 +80,6 @@ irqreturn_t scif_intr_handler(int irq, void *data)
return IRQ_HANDLED;
}
static int scif_peer_probe(struct scif_peer_dev *spdev)
{
struct scif_dev *scifdev = &scif_dev[spdev->dnode];
mutex_lock(&scif_info.conflock);
scif_info.total++;
scif_info.maxid = max_t(u32, spdev->dnode, scif_info.maxid);
mutex_unlock(&scif_info.conflock);
rcu_assign_pointer(scifdev->spdev, spdev);
/* In the future SCIF kernel client devices will be added here */
return 0;
}
static void scif_peer_remove(struct scif_peer_dev *spdev)
{
struct scif_dev *scifdev = &scif_dev[spdev->dnode];
/* In the future SCIF kernel client devices will be removed here */
spdev = rcu_dereference(scifdev->spdev);
if (spdev)
RCU_INIT_POINTER(scifdev->spdev, NULL);
synchronize_rcu();
mutex_lock(&scif_info.conflock);
scif_info.total--;
mutex_unlock(&scif_info.conflock);
}
static void scif_qp_setup_handler(struct work_struct *work)
{
struct scif_dev *scifdev = container_of(work, struct scif_dev,
......@@ -139,20 +110,13 @@ static void scif_qp_setup_handler(struct work_struct *work)
}
}
static int scif_setup_scifdev(struct scif_hw_dev *sdev)
static int scif_setup_scifdev(void)
{
/* We support a maximum of 129 SCIF nodes including the mgmt node */
#define MAX_SCIF_NODES 129
int i;
u8 num_nodes;
u8 num_nodes = MAX_SCIF_NODES;
if (sdev->snode) {
struct mic_bootparam __iomem *bp = sdev->rdp;
num_nodes = ioread8(&bp->tot_nodes);
} else {
struct mic_bootparam *bp = sdev->dp;
num_nodes = bp->tot_nodes;
}
scif_dev = kcalloc(num_nodes, sizeof(*scif_dev), GFP_KERNEL);
if (!scif_dev)
return -ENOMEM;
......@@ -163,7 +127,7 @@ static int scif_setup_scifdev(struct scif_hw_dev *sdev)
scifdev->exit = OP_IDLE;
init_waitqueue_head(&scifdev->disconn_wq);
mutex_init(&scifdev->lock);
INIT_WORK(&scifdev->init_msg_work, scif_qp_response_ack);
INIT_WORK(&scifdev->peer_add_work, scif_add_peer_device);
INIT_DELAYED_WORK(&scifdev->p2p_dwork,
scif_poll_qp_state);
INIT_DELAYED_WORK(&scifdev->qp_dwork,
......@@ -181,27 +145,21 @@ static void scif_destroy_scifdev(void)
static int scif_probe(struct scif_hw_dev *sdev)
{
struct scif_dev *scifdev;
struct scif_dev *scifdev = &scif_dev[sdev->dnode];
int rc;
dev_set_drvdata(&sdev->dev, sdev);
scifdev->sdev = sdev;
if (1 == atomic_add_return(1, &g_loopb_cnt)) {
struct scif_dev *loopb_dev;
struct scif_dev *loopb_dev = &scif_dev[sdev->snode];
rc = scif_setup_scifdev(sdev);
if (rc)
goto exit;
scifdev = &scif_dev[sdev->dnode];
scifdev->sdev = sdev;
loopb_dev = &scif_dev[sdev->snode];
loopb_dev->sdev = sdev;
rc = scif_setup_loopback_qp(loopb_dev);
if (rc)
goto free_sdev;
} else {
scifdev = &scif_dev[sdev->dnode];
scifdev->sdev = sdev;
goto exit;
}
rc = scif_setup_intr_wq(scifdev);
if (rc)
goto destroy_loopb;
......@@ -237,8 +195,6 @@ static int scif_probe(struct scif_hw_dev *sdev)
destroy_loopb:
if (atomic_dec_and_test(&g_loopb_cnt))
scif_destroy_loopback_qp(&scif_dev[sdev->snode]);
free_sdev:
scif_destroy_scifdev();
exit:
return rc;
}
......@@ -290,13 +246,6 @@ static void scif_remove(struct scif_hw_dev *sdev)
scifdev->sdev = NULL;
}
static struct scif_peer_driver scif_peer_driver = {
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.probe = scif_peer_probe,
.remove = scif_peer_remove,
};
static struct scif_hw_dev_id id_table[] = {
{ MIC_SCIF_DEV, SCIF_DEV_ANY_ID },
{ 0 },
......@@ -312,6 +261,8 @@ static struct scif_driver scif_driver = {
static int _scif_init(void)
{
int rc;
spin_lock_init(&scif_info.eplock);
spin_lock_init(&scif_info.nb_connect_lock);
spin_lock_init(&scif_info.port_lock);
......@@ -326,10 +277,15 @@ static int _scif_init(void)
init_waitqueue_head(&scif_info.exitwq);
scif_info.en_msg_log = 0;
scif_info.p2p_enable = 1;
rc = scif_setup_scifdev();
if (rc)
goto error;
INIT_WORK(&scif_info.misc_work, scif_misc_handler);
INIT_WORK(&scif_info.conn_work, scif_conn_handler);
idr_init(&scif_ports);
return 0;
error:
return rc;
}
static void _scif_exit(void)
......@@ -347,12 +303,9 @@ static int __init scif_init(void)
rc = scif_peer_bus_init();
if (rc)
goto exit;
rc = scif_peer_register_driver(&scif_peer_driver);
if (rc)
goto peer_bus_exit;
rc = scif_register_driver(&scif_driver);
if (rc)
goto unreg_scif_peer;
goto peer_bus_exit;
rc = misc_register(mdev);
if (rc)
goto unreg_scif;
......@@ -360,8 +313,6 @@ static int __init scif_init(void)
return 0;
unreg_scif:
scif_unregister_driver(&scif_driver);
unreg_scif_peer:
scif_peer_unregister_driver(&scif_peer_driver);
peer_bus_exit:
scif_peer_bus_exit();
exit:
......@@ -374,7 +325,6 @@ static void __exit scif_exit(void)
scif_exit_debugfs();
misc_deregister(&scif_info.mdev);
scif_unregister_driver(&scif_driver);
scif_peer_unregister_driver(&scif_peer_driver);
scif_peer_bus_exit();
_scif_exit();
}
......
......@@ -140,7 +140,7 @@ struct scif_p2p_info {
* @db: doorbell the peer will trigger to generate an interrupt on self
* @rdb: Doorbell to trigger on the peer to generate an interrupt on the peer
* @cookie: Cookie received while registering the interrupt handler
* init_msg_work: work scheduled for SCIF_INIT message processing
* @peer_add_work: Work for handling device_add for peer devices
* @p2p_dwork: Delayed work to enable polling for P2P state
* @qp_dwork: Delayed work for enabling polling for remote QP information
* @p2p_retry: Number of times to retry polling of P2P state
......@@ -166,7 +166,7 @@ struct scif_dev {
int db;
int rdb;
struct mic_irq *cookie;
struct work_struct init_msg_work;
struct work_struct peer_add_work;
struct delayed_work p2p_dwork;
struct delayed_work qp_dwork;
int p2p_retry;
......@@ -183,6 +183,7 @@ struct scif_dev {
extern struct scif_info scif_info;
extern struct idr scif_ports;
extern struct bus_type scif_peer_bus;
extern struct scif_dev *scif_dev;
extern const struct file_operations scif_fops;
extern const struct file_operations scif_anon_fops;
......
......@@ -147,14 +147,8 @@ void scif_cleanup_scifdev(struct scif_dev *dev)
void scif_handle_remove_node(int node)
{
struct scif_dev *scifdev = &scif_dev[node];
struct scif_peer_dev *spdev;
rcu_read_lock();
spdev = rcu_dereference(scifdev->spdev);
rcu_read_unlock();
if (spdev)
scif_peer_unregister_device(spdev);
else
if (scif_peer_unregister_device(scifdev))
scif_send_acks(scifdev);
}
......
......@@ -259,6 +259,11 @@ int scif_setup_qp_connect_response(struct scif_dev *scifdev,
&qp->remote_qp->local_write,
r_buf,
get_count_order(remote_size));
/*
* Because the node QP may already be processing an INIT message, set
* the read pointer so the cached read offset isn't lost
*/
qp->remote_qp->local_read = qp->inbound_q.current_read_offset;
/*
* resetup the inbound_q now that we know where the
* inbound_read really is.
......@@ -529,27 +534,6 @@ static void scif_p2p_setup(void)
}
}
void scif_qp_response_ack(struct work_struct *work)
{
struct scif_dev *scifdev = container_of(work, struct scif_dev,
init_msg_work);
struct scif_peer_dev *spdev;
/* Drop the INIT message if it has already been received */
if (_scifdev_alive(scifdev))
return;
spdev = scif_peer_register_device(scifdev);
if (IS_ERR(spdev))
return;
if (scif_is_mgmt_node()) {
mutex_lock(&scif_info.conflock);
scif_p2p_setup();
mutex_unlock(&scif_info.conflock);
}
}
static char *message_types[] = {"BAD",
"INIT",
"EXIT",
......@@ -682,13 +666,14 @@ scif_init(struct scif_dev *scifdev, struct scifmsg *msg)
* address to complete initializing the inbound_q.
*/
flush_delayed_work(&scifdev->qp_dwork);
/*
* Delegate the peer device registration to a workqueue, otherwise if
* SCIF client probe (called during peer device registration) calls
* scif_connect(..), it will block the message processing thread causing
* a deadlock.
*/
schedule_work(&scifdev->init_msg_work);
scif_peer_register_device(scifdev);
if (scif_is_mgmt_node()) {
mutex_lock(&scif_info.conflock);
scif_p2p_setup();
mutex_unlock(&scif_info.conflock);
}
}
/**
......@@ -838,13 +823,13 @@ void scif_poll_qp_state(struct work_struct *work)
msecs_to_jiffies(SCIF_NODE_QP_TIMEOUT));
return;
}
scif_peer_register_device(peerdev);
return;
timeout:
dev_err(&peerdev->sdev->dev,
"%s %d remote node %d offline, state = 0x%x\n",
__func__, __LINE__, peerdev->node, qp->qp_state);
qp->remote_qp->qp_state = SCIF_QP_OFFLINE;
scif_peer_unregister_device(peerdev);
scif_cleanup_scifdev(peerdev);
}
......@@ -894,6 +879,9 @@ scif_node_add_ack(struct scif_dev *scifdev, struct scifmsg *msg)
goto local_error;
peerdev->rdb = msg->payload[2];
qp->remote_qp->qp_state = SCIF_QP_ONLINE;
scif_peer_register_device(peerdev);
schedule_delayed_work(&peerdev->p2p_dwork, 0);
return;
local_error:
......@@ -1169,7 +1157,6 @@ int scif_setup_loopback_qp(struct scif_dev *scifdev)
int err = 0;
void *local_q;
struct scif_qp *qp;
struct scif_peer_dev *spdev;
err = scif_setup_intr_wq(scifdev);
if (err)
......@@ -1216,15 +1203,11 @@ int scif_setup_loopback_qp(struct scif_dev *scifdev)
&qp->local_write,
local_q, get_count_order(SCIF_NODE_QP_SIZE));
scif_info.nodeid = scifdev->node;
spdev = scif_peer_register_device(scifdev);
if (IS_ERR(spdev)) {
err = PTR_ERR(spdev);
goto free_local_q;
}
scif_peer_register_device(scifdev);
scif_info.loopb_dev = scifdev;
return err;
free_local_q:
kfree(local_q);
free_qpairs:
kfree(scifdev->qpairs);
destroy_loopb_wq:
......@@ -1243,13 +1226,7 @@ int scif_setup_loopback_qp(struct scif_dev *scifdev)
*/
int scif_destroy_loopback_qp(struct scif_dev *scifdev)
{
struct scif_peer_dev *spdev;
rcu_read_lock();
spdev = rcu_dereference(scifdev->spdev);
rcu_read_unlock();
if (spdev)
scif_peer_unregister_device(spdev);
scif_peer_unregister_device(scifdev);
destroy_workqueue(scif_info.loopb_wq);
scif_destroy_intr_wq(scifdev);
kfree(scifdev->qpairs->outbound_q.rb_base);
......
......@@ -24,93 +24,138 @@ dev_to_scif_peer(struct device *dev)
return container_of(dev, struct scif_peer_dev, dev);
}
static inline struct scif_peer_driver *
drv_to_scif_peer(struct device_driver *drv)
{
return container_of(drv, struct scif_peer_driver, driver);
}
struct bus_type scif_peer_bus = {
.name = "scif_peer_bus",
};
static int scif_peer_dev_match(struct device *dv, struct device_driver *dr)
static void scif_peer_release_dev(struct device *d)
{
return !strncmp(dev_name(dv), dr->name, 4);
struct scif_peer_dev *sdev = dev_to_scif_peer(d);
struct scif_dev *scifdev = &scif_dev[sdev->dnode];
scif_cleanup_scifdev(scifdev);
kfree(sdev);
}
static int scif_peer_dev_probe(struct device *d)
static int scif_peer_initialize_device(struct scif_dev *scifdev)
{
struct scif_peer_dev *dev = dev_to_scif_peer(d);
struct scif_peer_driver *drv = drv_to_scif_peer(dev->dev.driver);
struct scif_peer_dev *spdev;
int ret;
return drv->probe(dev);
}
spdev = kzalloc(sizeof(*spdev), GFP_KERNEL);
if (!spdev) {
ret = -ENOMEM;
goto err;
}
static int scif_peer_dev_remove(struct device *d)
{
struct scif_peer_dev *dev = dev_to_scif_peer(d);
struct scif_peer_driver *drv = drv_to_scif_peer(dev->dev.driver);
spdev->dev.parent = scifdev->sdev->dev.parent;
spdev->dev.release = scif_peer_release_dev;
spdev->dnode = scifdev->node;
spdev->dev.bus = &scif_peer_bus;
dev_set_name(&spdev->dev, "scif_peer-dev%u", spdev->dnode);
device_initialize(&spdev->dev);
get_device(&spdev->dev);
rcu_assign_pointer(scifdev->spdev, spdev);
drv->remove(dev);
mutex_lock(&scif_info.conflock);
scif_info.total++;
scif_info.maxid = max_t(u32, spdev->dnode, scif_info.maxid);
mutex_unlock(&scif_info.conflock);
return 0;
err:
dev_err(&scifdev->sdev->dev,
"dnode %d: initialize_device rc %d\n", scifdev->node, ret);
return ret;
}
static struct bus_type scif_peer_bus = {
.name = "scif_peer_bus",
.match = scif_peer_dev_match,
.probe = scif_peer_dev_probe,
.remove = scif_peer_dev_remove,
};
int scif_peer_register_driver(struct scif_peer_driver *driver)
static int scif_peer_add_device(struct scif_dev *scifdev)
{
driver->driver.bus = &scif_peer_bus;
return driver_register(&driver->driver);
struct scif_peer_dev *spdev = rcu_dereference(scifdev->spdev);
int ret;
ret = device_add(&spdev->dev);
put_device(&spdev->dev);
if (ret) {
dev_err(&scifdev->sdev->dev,
"dnode %d: peer device_add failed\n", scifdev->node);
goto put_spdev;
}
dev_dbg(&spdev->dev, "Added peer dnode %d\n", spdev->dnode);
return 0;
put_spdev:
RCU_INIT_POINTER(scifdev->spdev, NULL);
synchronize_rcu();
put_device(&spdev->dev);
mutex_lock(&scif_info.conflock);
scif_info.total--;
mutex_unlock(&scif_info.conflock);
return ret;
}
void scif_peer_unregister_driver(struct scif_peer_driver *driver)
void scif_add_peer_device(struct work_struct *work)
{
driver_unregister(&driver->driver);
struct scif_dev *scifdev = container_of(work, struct scif_dev,
peer_add_work);
scif_peer_add_device(scifdev);
}
static void scif_peer_release_dev(struct device *d)
/*
* Peer device registration is split into a device_initialize and a device_add.
* The reason for doing this is as follows: First, peer device registration
* itself cannot be done in the message processing thread and must be delegated
* to another workqueue, otherwise if SCIF client probe, called during peer
* device registration, calls scif_connect(..), it will block the message
* processing thread causing a deadlock. Next, device_initialize is done in the
* "top-half" message processing thread and device_add in the "bottom-half"
* workqueue. If this is not done, SCIF_CNCT_REQ message processing executing
* concurrently with SCIF_INIT message processing is unable to get a reference
* on the peer device, thereby failing the connect request.
*/
void scif_peer_register_device(struct scif_dev *scifdev)
{
struct scif_peer_dev *sdev = dev_to_scif_peer(d);
struct scif_dev *scifdev = &scif_dev[sdev->dnode];
int ret;
scif_cleanup_scifdev(scifdev);
kfree(sdev);
mutex_lock(&scifdev->lock);
ret = scif_peer_initialize_device(scifdev);
if (ret)
goto exit;
schedule_work(&scifdev->peer_add_work);
exit:
mutex_unlock(&scifdev->lock);
}
struct scif_peer_dev *
scif_peer_register_device(struct scif_dev *scifdev)
int scif_peer_unregister_device(struct scif_dev *scifdev)
{
int ret;
struct scif_peer_dev *spdev;
spdev = kzalloc(sizeof(*spdev), GFP_KERNEL);
if (!spdev)
return ERR_PTR(-ENOMEM);
spdev->dev.parent = scifdev->sdev->dev.parent;
spdev->dev.release = scif_peer_release_dev;
spdev->dnode = scifdev->node;
spdev->dev.bus = &scif_peer_bus;
mutex_lock(&scifdev->lock);
/* Flush work to ensure device register is complete */
flush_work(&scifdev->peer_add_work);
dev_set_name(&spdev->dev, "scif_peer-dev%u", spdev->dnode);
/*
* device_register() causes the bus infrastructure to look for a
* matching driver.
* Continue holding scifdev->lock since theoretically unregister_device
* can be called simultaneously from multiple threads
*/
ret = device_register(&spdev->dev);
if (ret)
goto free_spdev;
return spdev;
free_spdev:
kfree(spdev);
return ERR_PTR(ret);
}
void scif_peer_unregister_device(struct scif_peer_dev *sdev)
{
device_unregister(&sdev->dev);
spdev = rcu_dereference(scifdev->spdev);
if (!spdev) {
mutex_unlock(&scifdev->lock);
return -ENODEV;
}
RCU_INIT_POINTER(scifdev->spdev, NULL);
synchronize_rcu();
mutex_unlock(&scifdev->lock);
dev_dbg(&spdev->dev, "Removing peer dnode %d\n", spdev->dnode);
device_unregister(&spdev->dev);
mutex_lock(&scif_info.conflock);
scif_info.total--;
mutex_unlock(&scif_info.conflock);
return 0;
}
int scif_peer_bus_init(void)
......
......@@ -19,47 +19,13 @@
#include <linux/device.h>
#include <linux/mic_common.h>
/*
* Peer devices show up as PCIe devices for the mgmt node but not the cards.
* The mgmt node discovers all the cards on the PCIe bus and informs the other
* cards about their peers. Upon notification of a peer a node adds a peer
* device to the peer bus to maintain symmetry in the way devices are
* discovered across all nodes in the SCIF network.
*/
/**
* scif_peer_dev - representation of a peer SCIF device
* @dev: underlying device
* @dnode - The destination node which this device will communicate with.
*/
struct scif_peer_dev {
struct device dev;
u8 dnode;
};
/**
* scif_peer_driver - operations for a scif_peer I/O driver
* @driver: underlying device driver (populate name and owner).
* @id_table: the ids serviced by this driver.
* @probe: the function to call when a device is found. Returns 0 or -errno.
* @remove: the function to call when a device is removed.
*/
struct scif_peer_driver {
struct device_driver driver;
const struct scif_peer_dev_id *id_table;
int (*probe)(struct scif_peer_dev *dev);
void (*remove)(struct scif_peer_dev *dev);
};
#include <linux/scif.h>
struct scif_dev;
int scif_peer_register_driver(struct scif_peer_driver *driver);
void scif_peer_unregister_driver(struct scif_peer_driver *driver);
struct scif_peer_dev *scif_peer_register_device(struct scif_dev *sdev);
void scif_peer_unregister_device(struct scif_peer_dev *sdev);
void scif_add_peer_device(struct work_struct *work);
void scif_peer_register_device(struct scif_dev *sdev);
int scif_peer_unregister_device(struct scif_dev *scifdev);
int scif_peer_bus_init(void);
void scif_peer_bus_exit(void);
#endif /* _SCIF_PEER_BUS_H */
......@@ -55,6 +55,7 @@
#include <linux/types.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/scif_ioctl.h>
#define SCIF_ACCEPT_SYNC 1
......@@ -105,6 +106,37 @@ struct scif_pollepd {
short revents;
};
/**
* scif_peer_dev - representation of a peer SCIF device
*
* Peer devices show up as PCIe devices for the mgmt node but not the cards.
* The mgmt node discovers all the cards on the PCIe bus and informs the other
* cards about their peers. Upon notification of a peer a node adds a peer
* device to the peer bus to maintain symmetry in the way devices are
* discovered across all nodes in the SCIF network.
*
* @dev: underlying device
* @dnode - The destination node which this device will communicate with.
*/
struct scif_peer_dev {
struct device dev;
u8 dnode;
};
/**
* scif_client - representation of a SCIF client
* @name: client name
* @probe - client method called when a peer device is registered
* @remove - client method called when a peer device is unregistered
* @si - subsys_interface used internally for implementing SCIF clients
*/
struct scif_client {
const char *name;
void (*probe)(struct scif_peer_dev *spdev);
void (*remove)(struct scif_peer_dev *spdev);
struct subsys_interface si;
};
#define SCIF_OPEN_FAILED ((scif_epd_t)-1)
#define SCIF_REGISTER_FAILED ((off_t)-1)
#define SCIF_MMAP_FAILED ((void *)-1)
......@@ -1064,4 +1096,30 @@ int scif_get_node_ids(u16 *nodes, int len, u16 *self);
*/
int scif_poll(struct scif_pollepd *epds, unsigned int nepds, long timeout);
/**
* scif_client_register() - Register a SCIF client
* @client: client to be registered
*
* scif_client_register() registers a SCIF client. The probe() method
* of the client is called when SCIF peer devices come online and the
* remove() method is called when the peer devices disappear.
*
* Return:
* Upon successful completion, scif_client_register() returns a non-negative
* value. Otherwise the return value is the same as subsys_interface_register()
* in the kernel.
*/
int scif_client_register(struct scif_client *client);
/**
* scif_client_unregister() - Unregister a SCIF client
* @client: client to be unregistered
*
* scif_client_unregister() unregisters a SCIF client.
*
* Return:
* None
*/
void scif_client_unregister(struct scif_client *client);
#endif /* __SCIF_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment