From: Daniel Mack <dan...@zonque.org>

This patch adds code for matches and notifications.

Notifications are broadcast messages generated by the kernel, which
notify subscribes when connections are created or destroyed, when
well-known-names have been claimed, released or changed ownership,
or when reply messages have timed out.

Matches are used to tell the kernel driver which broadcast messages
a connection is interested in. Matches can either be specific on one
of the kernel-generated notification types, or carry a bloom filter
mask to match against a message from userspace. The latter is a way
to pre-filter messages from other connections in order to mitigate
unnecessary wakeups.

Signed-off-by: Daniel Mack <dan...@zonque.org>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 drivers/misc/kdbus/match.c  | 521 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/misc/kdbus/match.h  |  30 +++
 drivers/misc/kdbus/notify.c | 235 ++++++++++++++++++++
 drivers/misc/kdbus/notify.h |  28 +++
 4 files changed, 814 insertions(+)
 create mode 100644 drivers/misc/kdbus/match.c
 create mode 100644 drivers/misc/kdbus/match.h
 create mode 100644 drivers/misc/kdbus/notify.c
 create mode 100644 drivers/misc/kdbus/notify.h

diff --git a/drivers/misc/kdbus/match.c b/drivers/misc/kdbus/match.c
new file mode 100644
index 000000000000..86458a642d07
--- /dev/null
+++ b/drivers/misc/kdbus/match.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2013-2014 Kay Sievers
+ * Copyright (C) 2013-2014 Greg Kroah-Hartman <gre...@linuxfoundation.org>
+ * Copyright (C) 2013-2014 Daniel Mack <dan...@zonque.org>
+ * Copyright (C) 2013-2014 David Herrmann <dh.herrm...@gmail.com>
+ * Copyright (C) 2013-2014 Linux Foundation
+ *
+ * kdbus is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/hash.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "bus.h"
+#include "connection.h"
+#include "endpoint.h"
+#include "item.h"
+#include "match.h"
+#include "message.h"
+
+/**
+ * struct kdbus_match_db - message filters
+ * @entries_list:      List of matches
+ * @entries_lock:      Match data lock
+ * @entries:           Number of entries in database
+ */
+struct kdbus_match_db {
+       struct list_head entries_list;
+       struct mutex entries_lock;
+       unsigned int entries;
+};
+
+/**
+ * struct kdbus_match_entry - a match database entry
+ * @cookie:            User-supplied cookie to lookup the entry
+ * @list_entry:                The list entry element for the db list
+ * @rules_list:                The list head for tracking rules of this entry
+ */
+struct kdbus_match_entry {
+       u64 cookie;
+       struct list_head list_entry;
+       struct list_head rules_list;
+};
+
+/**
+ * struct kdbus_bloom_mask - mask to match against filter
+ * @generations:       Number of generations carried
+ * @data:              Array of bloom bit fields
+ */
+struct kdbus_bloom_mask {
+       u64 generations;
+       u64 *data;
+};
+
+/**
+ * struct kdbus_match_rule - a rule appended to a match entry
+ * @type:              An item type to match agains
+ * @bloom_mask:                Bloom mask to match a message's filter against, 
used
+ *                     with KDBUS_ITEM_BLOOM_MASK
+ * @name:              Name to match against, used with KDBUS_ITEM_NAME,
+ *                     KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}
+ * @old_id:            ID to match against, used with
+ *                     KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE},
+ *                     KDBUS_ITEM_ID_REMOVE
+ * @new_id:            ID to match against, used with
+ *                     KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE},
+ *                     KDBUS_ITEM_ID_REMOVE
+ * @src_id:            ID to match against, used with KDBUS_ITEM_ID
+ * @rules_entry:       Entry in the entry's rules list
+ */
+struct kdbus_match_rule {
+       u64 type;
+       union {
+               struct kdbus_bloom_mask bloom_mask;
+               struct {
+                       char *name;
+                       u64 old_id;
+                       u64 new_id;
+               };
+               u64 src_id;
+       };
+       struct list_head rules_entry;
+};
+
+static void kdbus_match_rule_free(struct kdbus_match_rule *rule)
+{
+       switch (rule->type) {
+       case KDBUS_ITEM_BLOOM_MASK:
+               kfree(rule->bloom_mask.data);
+               break;
+
+       case KDBUS_ITEM_NAME:
+       case KDBUS_ITEM_NAME_ADD:
+       case KDBUS_ITEM_NAME_REMOVE:
+       case KDBUS_ITEM_NAME_CHANGE:
+               kfree(rule->name);
+               break;
+
+       case KDBUS_ITEM_ID:
+       case KDBUS_ITEM_ID_ADD:
+       case KDBUS_ITEM_ID_REMOVE:
+               break;
+
+       default:
+               BUG();
+       }
+
+       list_del(&rule->rules_entry);
+       kfree(rule);
+}
+
+static void kdbus_match_entry_free(struct kdbus_match_entry *entry)
+{
+       struct kdbus_match_rule *r, *tmp;
+
+       list_for_each_entry_safe(r, tmp, &entry->rules_list, rules_entry)
+               kdbus_match_rule_free(r);
+
+       list_del(&entry->list_entry);
+       kfree(entry);
+}
+
+/**
+ * kdbus_match_db_free() - free match db resources
+ * @db:                        The match database
+ */
+void kdbus_match_db_free(struct kdbus_match_db *db)
+{
+       struct kdbus_match_entry *entry, *tmp;
+
+       mutex_lock(&db->entries_lock);
+       list_for_each_entry_safe(entry, tmp, &db->entries_list, list_entry)
+               kdbus_match_entry_free(entry);
+       mutex_unlock(&db->entries_lock);
+
+       kfree(db);
+}
+
+/**
+ * kdbus_match_db_new() - create a new match database
+ * @db:                        Pointer location for the returned database
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int kdbus_match_db_new(struct kdbus_match_db **db)
+{
+       struct kdbus_match_db *d;
+
+       d = kzalloc(sizeof(*d), GFP_KERNEL);
+       if (!d)
+               return -ENOMEM;
+
+       mutex_init(&d->entries_lock);
+       INIT_LIST_HEAD(&d->entries_list);
+
+       *db = d;
+       return 0;
+}
+
+static bool kdbus_match_bloom(const struct kdbus_bloom_filter *filter,
+                             const struct kdbus_bloom_mask *mask,
+                             const struct kdbus_conn *conn)
+{
+       size_t n = conn->bus->bloom.size / sizeof(u64);
+       const u64 *m;
+       size_t i;
+
+       /*
+        * The message's filter carries a generation identifier, the
+        * match's mask possibly carries an array of multiple generations
+        * of the mask. Select the mask with the closest match of the
+        * filter's generation.
+        */
+       m = mask->data + (min(filter->generation, mask->generations - 1) * n);
+
+       /*
+        * The message's filter contains the messages properties,
+        * the match's mask contains the properties to look for in the
+        * message. Check the mask bit field against the filter bit field,
+        * if the message possibly carries the properties the connection
+        * has subscribed to.
+        */
+       for (i = 0; i < n; i++)
+               if ((filter->data[i] & m[i]) != m[i])
+                       return false;
+
+       return true;
+}
+
+static bool kdbus_match_rules(const struct kdbus_match_entry *entry,
+                             struct kdbus_conn *conn_src,
+                             struct kdbus_kmsg *kmsg)
+{
+       struct kdbus_match_rule *r;
+
+       /*
+        * Walk all the rules and bail out immediately
+        * if any of them is unsatisfied.
+        */
+
+       list_for_each_entry(r, &entry->rules_list, rules_entry) {
+               if (conn_src == NULL) {
+                       /* kernel notifications */
+
+                       if (kmsg->notify_type != r->type)
+                               return false;
+
+                       switch (r->type) {
+                       case KDBUS_ITEM_ID_ADD:
+                               if (r->new_id != KDBUS_MATCH_ID_ANY &&
+                                   r->new_id != kmsg->notify_new_id)
+                                       return false;
+
+                               break;
+
+                       case KDBUS_ITEM_ID_REMOVE:
+                               if (r->old_id != KDBUS_MATCH_ID_ANY &&
+                                   r->old_id != kmsg->notify_old_id)
+                                       return false;
+
+                               break;
+
+                       case KDBUS_ITEM_NAME_ADD:
+                       case KDBUS_ITEM_NAME_CHANGE:
+                       case KDBUS_ITEM_NAME_REMOVE:
+                               if ((r->old_id != KDBUS_MATCH_ID_ANY &&
+                                    r->old_id != kmsg->notify_old_id) ||
+                                   (r->new_id != KDBUS_MATCH_ID_ANY &&
+                                    r->new_id != kmsg->notify_new_id) ||
+                                   (r->name && kmsg->notify_name &&
+                                    strcmp(r->name, kmsg->notify_name) != 0))
+                                       return false;
+
+                               break;
+
+                       default:
+                               return false;
+                       }
+               } else {
+                       /* messages from userspace */
+
+                       switch (r->type) {
+                       case KDBUS_ITEM_BLOOM_MASK:
+                               if (!kdbus_match_bloom(kmsg->bloom_filter,
+                                                      &r->bloom_mask,
+                                                      conn_src))
+                                       return false;
+                               break;
+
+                       case KDBUS_ITEM_ID:
+                               if (r->src_id != conn_src->id &&
+                                   r->src_id != KDBUS_MATCH_ID_ANY)
+                                       return false;
+
+                               break;
+
+                       case KDBUS_ITEM_NAME:
+                               if (!kdbus_conn_has_name(conn_src, r->name))
+                                       return false;
+
+                               break;
+
+                       default:
+                               return false;
+                       }
+               }
+       }
+
+       return true;
+}
+
+/**
+ * kdbus_match_db_match_kmsg() - match a kmsg object agains the database 
entries
+ * @db:                        The match database
+ * @conn_src:          The connection object originating the message
+ * @kmsg:              The kmsg to perform the match on
+ *
+ * This function will walk through all the database entries previously uploaded
+ * with kdbus_match_db_add(). As soon as any of them has an all-satisfied rule
+ * set, this function will return true.
+ *
+ * Return: true if there was a matching database entry, false otherwise.
+ */
+bool kdbus_match_db_match_kmsg(struct kdbus_match_db *db,
+                              struct kdbus_conn *conn_src,
+                              struct kdbus_kmsg *kmsg)
+{
+       struct kdbus_match_entry *entry;
+       bool matched = false;
+
+       mutex_lock(&db->entries_lock);
+       list_for_each_entry(entry, &db->entries_list, list_entry) {
+               matched = kdbus_match_rules(entry, conn_src, kmsg);
+               if (matched)
+                       break;
+       }
+       mutex_unlock(&db->entries_lock);
+
+       return matched;
+}
+
+static int __kdbus_match_db_remove_unlocked(struct kdbus_match_db *db,
+                                           uint64_t cookie)
+{
+       struct kdbus_match_entry *entry, *tmp;
+       bool found = false;
+
+       list_for_each_entry_safe(entry, tmp, &db->entries_list, list_entry)
+               if (entry->cookie == cookie) {
+                       kdbus_match_entry_free(entry);
+                       --db->entries;
+                       found = true;
+               }
+
+       return found ? 0 : -ENOENT;
+}
+
+/**
+ * kdbus_match_db_add() - add an entry to the match database
+ * @conn:              The connection that was used in the ioctl call
+ * @cmd:               The command as provided by the ioctl call
+ *
+ * This function is used in the context of the KDBUS_CMD_MATCH_ADD ioctl
+ * interface.
+ *
+ * One call to this function (or one ioctl(KDBUS_CMD_MATCH_ADD), respectively,
+ * adds one new database entry with n rules attached to it. Each rule is
+ * described with an kdbus_item, and an entry is considered matching if all
+ * its rules are satisfied.
+ *
+ * The items attached to a kdbus_cmd_match struct have the following mapping:
+ *
+ * KDBUS_ITEM_BLOOM_MASK:      A bloom mask
+ * KDBUS_ITEM_NAME:            A connection's source name
+ * KDBUS_ITEM_ID:              A connection ID
+ * KDBUS_ITEM_NAME_ADD:
+ * KDBUS_ITEM_NAME_REMOVE:
+ * KDBUS_ITEM_NAME_CHANGE:     Well-known name changes, carry
+ *                             kdbus_notify_name_change
+ * KDBUS_ITEM_ID_ADD:
+ * KDBUS_ITEM_ID_REMOVE:       Connection ID changes, carry
+ *                             kdbus_notify_id_change
+ *
+ * For kdbus_notify_{id,name}_change structs, only the ID and name fields
+ * are looked at at when adding an entry. The flags are unused.
+ *
+ * Also note that KDBUS_ITEM_BLOOM_MASK, KDBUS_ITEM_NAME and KDBUS_ITEM_ID
+ * are used to match messages from userspace, while the others apply to
+ * kernel-generated notifications.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+int kdbus_match_db_add(struct kdbus_conn *conn,
+                      struct kdbus_cmd_match *cmd)
+{
+       struct kdbus_match_entry *entry = NULL;
+       struct kdbus_match_db *db = conn->match_db;
+       struct kdbus_item *item;
+       LIST_HEAD(list);
+       int ret = 0;
+
+       lockdep_assert_held(conn);
+
+       entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry) {
+               ret = -ENOMEM;
+               goto exit_free;
+       }
+
+       entry->cookie = cmd->cookie;
+       INIT_LIST_HEAD(&entry->list_entry);
+       INIT_LIST_HEAD(&entry->rules_list);
+
+       KDBUS_ITEMS_FOREACH(item, cmd->items, KDBUS_ITEMS_SIZE(cmd, items)) {
+               struct kdbus_match_rule *rule;
+               size_t size = item->size - offsetof(struct kdbus_item, data);
+
+               rule = kzalloc(sizeof(*rule), GFP_KERNEL);
+               if (!rule) {
+                       ret = -ENOMEM;
+                       break;
+               }
+
+               switch (item->type) {
+               case KDBUS_ITEM_BLOOM_MASK: {
+                       u64 generations;
+                       u64 remainder;
+
+                       generations = div64_u64_rem(size, conn->bus->bloom.size,
+                                                   &remainder);
+                       if (size < conn->bus->bloom.size ||
+                           remainder > 0) {
+                               ret = -EDOM;
+                               break;
+                       }
+
+                       rule->bloom_mask.data = kmemdup(item->data,
+                                                       size, GFP_KERNEL);
+                       if (!rule->bloom_mask.data) {
+                               ret = -ENOMEM;
+                               break;
+                       }
+
+                       /* we get an array of n generations of bloom masks */
+                       rule->bloom_mask.generations = generations;
+
+                       break;
+               }
+               case KDBUS_ITEM_NAME:
+                       ret = kdbus_item_validate_name(item);
+                       if (ret < 0)
+                               break;
+
+                       rule->name = kstrdup(item->str, GFP_KERNEL);
+                       if (!rule->name)
+                               ret = -ENOMEM;
+
+                       break;
+
+               case KDBUS_ITEM_ID:
+                       rule->src_id = item->id;
+                       break;
+
+               case KDBUS_ITEM_NAME_ADD:
+               case KDBUS_ITEM_NAME_REMOVE:
+               case KDBUS_ITEM_NAME_CHANGE: {
+                       rule->old_id = item->name_change.old_id.id;
+                       rule->new_id = item->name_change.new_id.id;
+
+                       if (size > sizeof(struct kdbus_notify_name_change)) {
+                               rule->name = kstrdup(item->name_change.name,
+                                                    GFP_KERNEL);
+                               if (!rule->name)
+                                       ret = -ENOMEM;
+                       }
+
+                       break;
+               }
+
+               case KDBUS_ITEM_ID_ADD:
+               case KDBUS_ITEM_ID_REMOVE:
+                       if (item->type == KDBUS_ITEM_ID_ADD)
+                               rule->new_id = item->id_change.id;
+                       else
+                               rule->old_id = item->id_change.id;
+
+                       break;
+
+               default:
+                       kfree(rule);
+                       continue;
+               }
+
+               if (ret < 0) {
+                       kfree(rule);
+                       break;
+               }
+
+               rule->type = item->type;
+
+               list_add_tail(&rule->rules_entry, &entry->rules_list);
+       }
+
+       mutex_lock(&db->entries_lock);
+
+       /* Remove any entry that has the same cookie as the current one. */
+       if (cmd->flags & KDBUS_MATCH_REPLACE)
+               __kdbus_match_db_remove_unlocked(db, entry->cookie);
+
+       /*
+        * If the above removal caught any entry, there will be room for the
+        * new one.
+        */
+       if (++db->entries > KDBUS_MATCH_MAX) {
+               --db->entries;
+               ret = -EMFILE;
+       }
+       if (ret == 0)
+               list_add_tail(&entry->list_entry, &db->entries_list);
+       else
+               kdbus_match_entry_free(entry);
+       mutex_unlock(&db->entries_lock);
+
+exit_free:
+       return ret;
+}
+
+/**
+ * kdbus_match_db_remove() - remove an entry from the match database
+ * @conn:              The connection that was used in the ioctl call
+ * @cmd:               Pointer to the match data structure
+ *
+ * This function is used in the context of the KDBUS_CMD_MATCH_REMOVE
+ * ioctl interface.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int kdbus_match_db_remove(struct kdbus_conn *conn,
+                         struct kdbus_cmd_match *cmd)
+{
+       struct kdbus_match_db *db = conn->match_db;
+       int ret;
+
+       lockdep_assert_held(conn);
+
+       mutex_lock(&db->entries_lock);
+       ret = __kdbus_match_db_remove_unlocked(db, cmd->cookie);
+       mutex_unlock(&db->entries_lock);
+
+       return ret;
+}
diff --git a/drivers/misc/kdbus/match.h b/drivers/misc/kdbus/match.h
new file mode 100644
index 000000000000..72888080a8d0
--- /dev/null
+++ b/drivers/misc/kdbus/match.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013-2014 Kay Sievers
+ * Copyright (C) 2013-2014 Greg Kroah-Hartman <gre...@linuxfoundation.org>
+ * Copyright (C) 2013-2014 Daniel Mack <dan...@zonque.org>
+ * Copyright (C) 2013-2014 David Herrmann <dh.herrm...@gmail.com>
+ * Copyright (C) 2013-2014 Linux Foundation
+ *
+ * kdbus is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#ifndef __KDBUS_MATCH_H
+#define __KDBUS_MATCH_H
+
+struct kdbus_conn;
+struct kdbus_kmsg;
+struct kdbus_match_db;
+
+int kdbus_match_db_new(struct kdbus_match_db **db);
+void kdbus_match_db_free(struct kdbus_match_db *db);
+int kdbus_match_db_add(struct kdbus_conn *conn,
+                      struct kdbus_cmd_match *cmd);
+int kdbus_match_db_remove(struct kdbus_conn *conn,
+                         struct kdbus_cmd_match *cmd);
+bool kdbus_match_db_match_kmsg(struct kdbus_match_db *db,
+                              struct kdbus_conn *conn_src,
+                              struct kdbus_kmsg *kmsg);
+#endif
diff --git a/drivers/misc/kdbus/notify.c b/drivers/misc/kdbus/notify.c
new file mode 100644
index 000000000000..c68add64cbf0
--- /dev/null
+++ b/drivers/misc/kdbus/notify.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013-2014 Kay Sievers
+ * Copyright (C) 2013-2014 Greg Kroah-Hartman <gre...@linuxfoundation.org>
+ * Copyright (C) 2013-2014 Daniel Mack <dan...@zonque.org>
+ * Copyright (C) 2013-2014 David Herrmann <dh.herrm...@gmail.com>
+ * Copyright (C) 2013-2014 Linux Foundation
+ *
+ * kdbus is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "bus.h"
+#include "connection.h"
+#include "endpoint.h"
+#include "item.h"
+#include "message.h"
+#include "notify.h"
+
+static int kdbus_notify_reply(struct kdbus_bus *bus, u64 id,
+                             u64 cookie, u64 msg_type)
+{
+       struct kdbus_kmsg *kmsg = NULL;
+       int ret;
+
+       BUG_ON(id == 0);
+
+       ret = kdbus_kmsg_new(0, &kmsg);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * a kernel-generated notification can only contain one
+        * struct kdbus_item, so make a shortcut here for
+        * faster lookup in the match db.
+        */
+       kmsg->notify_type = msg_type;
+       kmsg->msg.dst_id = id;
+       kmsg->msg.src_id = KDBUS_SRC_ID_KERNEL;
+       kmsg->msg.payload_type = KDBUS_PAYLOAD_KERNEL;
+       kmsg->msg.cookie_reply = cookie;
+       kmsg->msg.items[0].type = msg_type;
+
+       spin_lock(&bus->notify_lock);
+       list_add_tail(&kmsg->queue_entry, &bus->notify_list);
+       spin_unlock(&bus->notify_lock);
+       return ret;
+}
+
+/**
+ * kdbus_notify_reply_timeout() - queue a timeout reply
+ * @bus:               Bus which queues the messages
+ * @id:                        The destination's connection ID
+ * @cookie:            The cookie to set in the reply.
+ *
+ * Queues a message that has a KDBUS_ITEM_REPLY_TIMEOUT item attached.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int kdbus_notify_reply_timeout(struct kdbus_bus *bus, u64 id, u64 cookie)
+{
+       return kdbus_notify_reply(bus, id, cookie, KDBUS_ITEM_REPLY_TIMEOUT);
+}
+
+/**
+ * kdbus_notify_reply_dead() - queue a 'dead' reply
+ * @bus:               Bus which queues the messages
+ * @id:                        The destination's connection ID
+ * @cookie:            The cookie to set in the reply.
+ *
+ * Queues a message that has a KDBUS_ITEM_REPLY_DEAD item attached.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int kdbus_notify_reply_dead(struct kdbus_bus *bus, u64 id, u64 cookie)
+{
+       return kdbus_notify_reply(bus, id, cookie, KDBUS_ITEM_REPLY_DEAD);
+}
+
+/**
+ * kdbus_notify_name_change() - queue a notification about a name owner change
+ * @bus:               Bus which queues the messages
+ * @type:              The type if the notification; KDBUS_ITEM_NAME_ADD,
+ *                     KDBUS_ITEM_NAME_CHANGE or KDBUS_ITEM_NAME_REMOVE
+ * @old_id:            The id of the connection that used to own the name
+ * @new_id:            The id of the new owner connection
+ * @old_flags:         The flags to pass in the KDBUS_ITEM flags field for
+ *                     the old owner
+ * @new_flags:         The flags to pass in the KDBUS_ITEM flags field for
+ *                     the new owner
+ * @name:              The name that was removed or assigned to a new owner
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int kdbus_notify_name_change(struct kdbus_bus *bus, u64 type,
+                            u64 old_id, u64 new_id,
+                            u64 old_flags, u64 new_flags,
+                            const char *name)
+{
+       struct kdbus_kmsg *kmsg = NULL;
+       size_t name_len, extra_size;
+       int ret;
+
+       name_len = strlen(name) + 1;
+       extra_size = sizeof(struct kdbus_notify_name_change) + name_len;
+       ret = kdbus_kmsg_new(extra_size, &kmsg);
+       if (ret < 0)
+               return ret;
+
+       kmsg->msg.dst_id = KDBUS_DST_ID_BROADCAST;
+       kmsg->msg.src_id = KDBUS_SRC_ID_KERNEL;
+       kmsg->msg.payload_type = KDBUS_PAYLOAD_KERNEL;
+       kmsg->notify_type = type;
+       kmsg->notify_old_id = old_id;
+       kmsg->notify_new_id = new_id;
+       kmsg->msg.items[0].type = type;
+       kmsg->msg.items[0].name_change.old_id.id = old_id;
+       kmsg->msg.items[0].name_change.old_id.flags = old_flags;
+       kmsg->msg.items[0].name_change.new_id.id = new_id;
+       kmsg->msg.items[0].name_change.new_id.flags = new_flags;
+       memcpy(kmsg->msg.items[0].name_change.name, name, name_len);
+       kmsg->notify_name = kmsg->msg.items[0].name_change.name;
+
+       spin_lock(&bus->notify_lock);
+       list_add_tail(&kmsg->queue_entry, &bus->notify_list);
+       spin_unlock(&bus->notify_lock);
+       return ret;
+}
+
+/**
+ * kdbus_notify_id_change() - queue a notification about a unique ID change
+ * @bus:               Bus which queues the messages
+ * @type:              The type if the notification; KDBUS_ITEM_ID_ADD or
+ *                     KDBUS_ITEM_ID_REMOVE
+ * @id:                        The id of the connection that was added or 
removed
+ * @flags:             The flags to pass in the KDBUS_ITEM flags field
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int kdbus_notify_id_change(struct kdbus_bus *bus, u64 type, u64 id, u64 flags)
+{
+       struct kdbus_kmsg *kmsg = NULL;
+       int ret;
+
+       ret = kdbus_kmsg_new(sizeof(struct kdbus_notify_id_change), &kmsg);
+       if (ret < 0)
+               return ret;
+
+       kmsg->msg.dst_id = KDBUS_DST_ID_BROADCAST;
+       kmsg->msg.src_id = KDBUS_SRC_ID_KERNEL;
+       kmsg->msg.payload_type = KDBUS_PAYLOAD_KERNEL;
+       kmsg->notify_type = type;
+
+       switch (type) {
+       case KDBUS_ITEM_ID_ADD:
+               kmsg->notify_new_id = id;
+               break;
+
+       case KDBUS_ITEM_ID_REMOVE:
+               kmsg->notify_old_id = id;
+               break;
+
+       default:
+               BUG();
+       }
+
+       kmsg->msg.items[0].type = type;
+       kmsg->msg.items[0].id_change.id = id;
+       kmsg->msg.items[0].id_change.flags = flags;
+
+       spin_lock(&bus->notify_lock);
+       list_add_tail(&kmsg->queue_entry, &bus->notify_list);
+       spin_unlock(&bus->notify_lock);
+       return ret;
+}
+
+/**
+ * kdbus_notify_flush() - send a list of collected messages
+ * @bus:               Bus which queues the messages
+ *
+ * The list is empty after sending the messages.
+ */
+void kdbus_notify_flush(struct kdbus_bus *bus)
+{
+       LIST_HEAD(notify_list);
+       struct kdbus_kmsg *kmsg, *tmp;
+       struct kdbus_ep *ep = NULL;
+
+       /* bus->ep is only valid as long as the bus is alive */
+       mutex_lock(&bus->lock);
+       if (!bus->disconnected)
+               ep = kdbus_ep_ref(bus->ep);
+       mutex_unlock(&bus->lock);
+
+       mutex_lock(&bus->notify_flush_lock);
+
+       spin_lock(&bus->notify_lock);
+       list_splice_init(&bus->notify_list, &notify_list);
+       spin_unlock(&bus->notify_lock);
+
+       list_for_each_entry_safe(kmsg, tmp, &notify_list, queue_entry) {
+               if (ep)
+                       kdbus_conn_kmsg_send(ep, NULL, kmsg);
+               list_del(&kmsg->queue_entry);
+               kdbus_kmsg_free(kmsg);
+       }
+
+       mutex_unlock(&bus->notify_flush_lock);
+
+       kdbus_ep_unref(ep);
+}
+
+/**
+ * kdbus_notify_free() - free a list of collected messages
+ * @bus:               Bus which queues the messages
+ */
+void kdbus_notify_free(struct kdbus_bus *bus)
+{
+       struct kdbus_kmsg *kmsg, *tmp;
+
+       list_for_each_entry_safe(kmsg, tmp, &bus->notify_list, queue_entry) {
+               list_del(&kmsg->queue_entry);
+               kdbus_kmsg_free(kmsg);
+       }
+}
diff --git a/drivers/misc/kdbus/notify.h b/drivers/misc/kdbus/notify.h
new file mode 100644
index 000000000000..f6ebd56e2dca
--- /dev/null
+++ b/drivers/misc/kdbus/notify.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013-2014 Kay Sievers
+ * Copyright (C) 2013-2014 Greg Kroah-Hartman <gre...@linuxfoundation.org>
+ * Copyright (C) 2013-2014 Daniel Mack <dan...@zonque.org>
+ * Copyright (C) 2013-2014 David Herrmann <dh.herrm...@gmail.com>
+ * Copyright (C) 2013-2014 Linux Foundation
+ *
+ * kdbus is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#ifndef __KDBUS_NOTIFY_H
+#define __KDBUS_NOTIFY_H
+
+struct kdbus_bus;
+
+int kdbus_notify_id_change(struct kdbus_bus *bus, u64 type, u64 id, u64 flags);
+int kdbus_notify_reply_timeout(struct kdbus_bus *bus, u64 id, u64 cookie);
+int kdbus_notify_reply_dead(struct kdbus_bus *bus, u64 id, u64 cookie);
+int kdbus_notify_name_change(struct kdbus_bus *bus, u64 type,
+                            u64 old_id, u64 new_id,
+                            u64 old_flags, u64 new_flags,
+                            const char *name);
+void kdbus_notify_flush(struct kdbus_bus *bus);
+void kdbus_notify_free(struct kdbus_bus *bus);
+#endif
-- 
2.1.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to