#include <linux/err.h>
#include <linux/ctype.h>
#include <linux/processor.h>
-#include <net/smc.h>
-
-#include <asm/debug.h>
#include "ism.h"
MODULE_DEVICE_TABLE(pci, ism_device_table);
static debug_info_t *ism_debug_info;
+static const struct smcd_ops ism_ops;
+
+#define NO_CLIENT 0xff /* must be >= MAX_CLIENTS */
+static struct ism_client *clients[MAX_CLIENTS]; /* use an array rather than */
+ /* a list for fast mapping */
+static u8 max_client;
+static DEFINE_SPINLOCK(clients_lock);
+struct ism_dev_list {
+ struct list_head list;
+ struct mutex mutex; /* protects ism device list */
+};
+
+static struct ism_dev_list ism_dev_list = {
+ .list = LIST_HEAD_INIT(ism_dev_list.list),
+ .mutex = __MUTEX_INITIALIZER(ism_dev_list.mutex),
+};
+
+int ism_register_client(struct ism_client *client)
+{
+ struct ism_dev *ism;
+ unsigned long flags;
+ int i, rc = -ENOSPC;
+
+ mutex_lock(&ism_dev_list.mutex);
+ spin_lock_irqsave(&clients_lock, flags);
+ for (i = 0; i < MAX_CLIENTS; ++i) {
+ if (!clients[i]) {
+ clients[i] = client;
+ client->id = i;
+ if (i == max_client)
+ max_client++;
+ rc = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&clients_lock, flags);
+ if (i < MAX_CLIENTS) {
+ /* initialize with all devices that we got so far */
+ list_for_each_entry(ism, &ism_dev_list.list, list) {
+ ism->priv[i] = NULL;
+ client->add(ism);
+ }
+ }
+ mutex_unlock(&ism_dev_list.mutex);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(ism_register_client);
+
+int ism_unregister_client(struct ism_client *client)
+{
+ struct ism_dev *ism;
+ unsigned long flags;
+ int rc = 0;
+
+ mutex_lock(&ism_dev_list.mutex);
+ spin_lock_irqsave(&clients_lock, flags);
+ clients[client->id] = NULL;
+ if (client->id + 1 == max_client)
+ max_client--;
+ spin_unlock_irqrestore(&clients_lock, flags);
+ list_for_each_entry(ism, &ism_dev_list.list, list) {
+ for (int i = 0; i < ISM_NR_DMBS; ++i) {
+ if (ism->sba_client_arr[i] == client->id) {
+ pr_err("%s: attempt to unregister client '%s'"
+ "with registered dmb(s)\n", __func__,
+ client->name);
+ rc = -EBUSY;
+ goto out;
+ }
+ }
+ }
+out:
+ mutex_unlock(&ism_dev_list.mutex);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(ism_unregister_client);
static int ism_cmd(struct ism_dev *ism, void *cmd)
{
if (ret)
goto out;
- ism->smcd->local_gid = cmd.response.gid;
+ ism->local_gid = cmd.response.gid;
out:
return ret;
}
static void ism_handle_event(struct ism_dev *ism)
{
- struct smcd_event *entry;
+ struct ism_event *entry;
+ int i;
while ((ism->ieq_idx + 1) != READ_ONCE(ism->ieq->header.idx)) {
if (++(ism->ieq_idx) == ARRAY_SIZE(ism->ieq->entry))
entry = &ism->ieq->entry[ism->ieq_idx];
debug_event(ism_debug_info, 2, entry, sizeof(*entry));
- smcd_handle_event(ism->smcd, entry);
+ spin_lock(&clients_lock);
+ for (i = 0; i < max_client; ++i)
+ if (clients[i])
+ clients[i]->handle_event(ism, entry);
+ spin_unlock(&clients_lock);
}
}
static irqreturn_t ism_handle_irq(int irq, void *data)
{
struct ism_dev *ism = data;
+ struct ism_client *clt;
unsigned long bit, end;
unsigned long *bv;
u16 dmbemask;
dmbemask = ism->sba->dmbe_mask[bit + ISM_DMB_BIT_OFFSET];
ism->sba->dmbe_mask[bit + ISM_DMB_BIT_OFFSET] = 0;
barrier();
- smcd_handle_irq(ism->smcd, bit + ISM_DMB_BIT_OFFSET, dmbemask);
+ clt = clients[ism->sba_client_arr[bit]];
+ clt->handle_irq(ism, bit + ISM_DMB_BIT_OFFSET, dmbemask);
}
if (ism->sba->e) {
.get_chid = ism_get_chid,
};
+static void ism_dev_add_work_func(struct work_struct *work)
+{
+ struct ism_client *client = container_of(work, struct ism_client,
+ add_work);
+
+ client->add(client->tgt_ism);
+ atomic_dec(&client->tgt_ism->add_dev_cnt);
+ wake_up(&client->tgt_ism->waitq);
+}
+
static int ism_dev_init(struct ism_dev *ism)
{
struct pci_dev *pdev = ism->pdev;
- int ret;
+ unsigned long flags;
+ int i, ret;
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (ret <= 0)
/* hardware is V2 capable */
ism_create_system_eid();
+ init_waitqueue_head(&ism->waitq);
+ atomic_set(&ism->free_clients_cnt, 0);
+ atomic_set(&ism->add_dev_cnt, 0);
+
+ wait_event(ism->waitq, !atomic_read(&ism->add_dev_cnt));
+ spin_lock_irqsave(&clients_lock, flags);
+ for (i = 0; i < max_client; ++i)
+ if (clients[i]) {
+ INIT_WORK(&clients[i]->add_work,
+ ism_dev_add_work_func);
+ clients[i]->tgt_ism = ism;
+ atomic_inc(&ism->add_dev_cnt);
+ schedule_work(&clients[i]->add_work);
+ }
+ spin_unlock_irqrestore(&clients_lock, flags);
+
+ wait_event(ism->waitq, !atomic_read(&ism->add_dev_cnt));
+
+ mutex_lock(&ism_dev_list.mutex);
+ list_add(&ism->list, &ism_dev_list.list);
+ mutex_unlock(&ism_dev_list.mutex);
+
ret = smcd_register_dev(ism->smcd);
if (ret)
goto unreg_ieq;
return ret;
}
+static void ism_dev_remove_work_func(struct work_struct *work)
+{
+ struct ism_client *client = container_of(work, struct ism_client,
+ remove_work);
+
+ client->remove(client->tgt_ism);
+ atomic_dec(&client->tgt_ism->free_clients_cnt);
+ wake_up(&client->tgt_ism->waitq);
+}
+
+/* Callers must hold ism_dev_list.mutex */
static void ism_dev_exit(struct ism_dev *ism)
{
struct pci_dev *pdev = ism->pdev;
+ unsigned long flags;
+ int i;
+
+ wait_event(ism->waitq, !atomic_read(&ism->free_clients_cnt));
+ spin_lock_irqsave(&clients_lock, flags);
+ for (i = 0; i < max_client; ++i)
+ if (clients[i]) {
+ INIT_WORK(&clients[i]->remove_work,
+ ism_dev_remove_work_func);
+ clients[i]->tgt_ism = ism;
+ atomic_inc(&ism->free_clients_cnt);
+ schedule_work(&clients[i]->remove_work);
+ }
+ spin_unlock_irqrestore(&clients_lock, flags);
+
+ wait_event(ism->waitq, !atomic_read(&ism->free_clients_cnt));
smcd_unregister_dev(ism->smcd);
if (SYSTEM_EID.serial_number[0] != '0' ||
unregister_sba(ism);
free_irq(pci_irq_vector(pdev, 0), ism);
pci_free_irq_vectors(pdev);
+ list_del_init(&ism->list);
}
static void ism_remove(struct pci_dev *pdev)
{
struct ism_dev *ism = dev_get_drvdata(&pdev->dev);
+ mutex_lock(&ism_dev_list.mutex);
ism_dev_exit(ism);
+ mutex_unlock(&ism_dev_list.mutex);
smcd_free_dev(ism->smcd);
pci_clear_master(pdev);
pci_release_mem_regions(pdev);
pci_disable_device(pdev);
+ device_del(&ism->dev);
dev_set_drvdata(&pdev->dev, NULL);
kfree(ism);
}
if (!ism_debug_info)
return -ENODEV;
+ memset(clients, 0, sizeof(clients));
+ max_client = 0;
debug_register_view(ism_debug_info, &debug_hex_ascii_view);
ret = pci_register_driver(&ism_driver);
if (ret)
static void __exit ism_exit(void)
{
+ struct ism_dev *ism;
+
+ mutex_lock(&ism_dev_list.mutex);
+ list_for_each_entry(ism, &ism_dev_list.list, list) {
+ ism_dev_exit(ism);
+ }
+ mutex_unlock(&ism_dev_list.mutex);
+
pci_unregister_driver(&ism_driver);
debug_unregister(ism_debug_info);
}
#ifndef _ISM_H
#define _ISM_H
+#include <linux/workqueue.h>
+
struct ism_dmb {
u64 dmb_tok;
u64 rgid;
dma_addr_t dma_addr;
};
+/* Unless we gain unexpected popularity, this limit should hold for a while */
+#define MAX_CLIENTS 8
+#define ISM_NR_DMBS 1920
+
+struct ism_dev {
+ spinlock_t lock; /* protects the ism device */
+ struct list_head list;
+ struct pci_dev *pdev;
+ struct smcd_dev *smcd;
+
+ struct ism_sba *sba;
+ dma_addr_t sba_dma_addr;
+ DECLARE_BITMAP(sba_bitmap, ISM_NR_DMBS);
+ u8 *sba_client_arr; /* entries are indices into 'clients' array */
+ void *priv[MAX_CLIENTS];
+
+ struct ism_eq *ieq;
+ dma_addr_t ieq_dma_addr;
+
+ struct device dev;
+ u64 local_gid;
+ int ieq_idx;
+
+ atomic_t free_clients_cnt;
+ atomic_t add_dev_cnt;
+ wait_queue_head_t waitq;
+};
+
+struct ism_event {
+ u32 type;
+ u32 code;
+ u64 tok;
+ u64 time;
+ u64 info;
+};
+
+struct ism_client {
+ const char *name;
+ void (*add)(struct ism_dev *dev);
+ void (*remove)(struct ism_dev *dev);
+ void (*handle_event)(struct ism_dev *dev, struct ism_event *event);
+ /* Parameter dmbemask contains a bit vector with updated DMBEs, if sent
+ * via ism_move_data(). Callback function must handle all active bits
+ * indicated by dmbemask.
+ */
+ void (*handle_irq)(struct ism_dev *dev, unsigned int bit, u16 dmbemask);
+ /* Private area - don't touch! */
+ struct work_struct remove_work;
+ struct work_struct add_work;
+ struct ism_dev *tgt_ism;
+ u8 id;
+};
+
+int ism_register_client(struct ism_client *client);
+int ism_unregister_client(struct ism_client *client);
+static inline void *ism_get_priv(struct ism_dev *dev,
+ struct ism_client *client) {
+ return dev->priv[client->id];
+}
+
+static inline void ism_set_priv(struct ism_dev *dev, struct ism_client *client,
+ void *priv) {
+ dev->priv[client->id] = priv;
+}
+
#endif /* _ISM_H */