summaryrefslogtreecommitdiff
path: root/lib/mc_rx.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mc_rx.c')
-rw-r--r--lib/mc_rx.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/lib/mc_rx.c b/lib/mc_rx.c
new file mode 100644
index 0000000..c34d687
--- /dev/null
+++ b/lib/mc_rx.c
@@ -0,0 +1,535 @@
+/*
+ * Linux WiMax
+ * Framework for reading from multicast groups
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @defgroup mc_rx Reading form Generic Netlink multicast groups
+ *
+ * The WiMAX stack sends asynchronous traffic (notifications and
+ * messages) to user space through Generic Netlink multicast groups;
+ * thus, when reading that traffic from the kernel, libwimaxll
+ * actually reads from a generic netlink multicast group.
+ *
+ * This allows the kernel to send a single notification that can be
+ * received by an undetermined (and unbound) number of listeners. As
+ * well, this also allows a very flexible way to multiplex different
+ * channels without affecting all the listeners.
+ *
+ * What is called a \e pipe is mapped over one of these multicast
+ * groups.
+ *
+ * \b Example:
+ *
+ * If a driver wants to send tracing information to an application in
+ * user space for analysis, it can create a new \e pipe (Generic
+ * Netlink multicast group) and send it over there.
+ *
+ * The application can listen to it and its traffic volume won't
+ * affect other applications listening to other events coming from the
+ * same device. Some of these other applications could not be ready
+ * ready to cope with such a high traffic.
+ *
+ * If the same model were implemented just using different netlink
+ * messages, all applications listening to events from the driver
+ * would be awakened every time any kind of message were sent, even if
+ * they do not need to listen to some of those messages.
+ *
+ * \warning This is a \b very \b low level interface that is for
+ * internal use.
+ *
+ * \warning If you have to use it in an application, it probably means
+ * something is wrong.
+ *
+ * \warning You might want to use higher level messaging interfaces,
+ * such as the \ref the_pipe_interface_group "the pipe interface"
+ * or the \ref the_messaging_interface "the messaging interface".
+ *
+ * \section usage Usage
+ *
+ * The functions provided by this interface are almost identical than
+ * those of the \ref the_pipe_interface_group "pipe interface". The
+ * main difference is that wimaxll_mc_rx_read() operates at a lower
+ * level.
+ *
+ * \code
+ * int mc_handle;
+ * ssize_t bytes;
+ * ...
+ * mc_handle = wimaxll_mc_rx_open(wimaxll_handle, "name");
+ * ...
+ * bytes = wimaxll_mc_rx_read(wimaxll_handle, mc_handle);
+ * ...
+ * wimaxll_mc_rx_close(wimaxll_handle, mc_handle);
+ * \endcode
+ *
+ * \a my_callback is a function that will be called for every valid
+ * message received from the kernel on a single call to
+ * wimaxll_mc_rx_read().
+ *
+ * Internally, each \e open pipe/multicast-group contains the list of
+ * callbacks for each known message. This is used a look up table for
+ * executing them on reception.
+ *
+ * \section roadmap Roadmap
+ *
+ * \code
+ *
+ * wimaxll_mc_rx_open()
+ * wimaxll_mc_idx_by_name()
+ *
+ * wimaxll_mc_rx_read()
+ * nl_recvmsgs()
+ * wimaxll_seq_check_cb()
+ * wimaxll_gnl_error_cb()
+ * wimaxll_gnl_cb()
+ * wimaxll_gnl_handle_state_change()
+ * wimaxll_gnl_handle_msg_to_user()
+ * wimaxll_mch_maybe_set_result()
+ *
+ * wimaxll_mc_rx_fd()
+ * __wimaxll_mc_handle()
+ * wimaxll_mc_rx_close()
+ * \endcode
+ */
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+#include <linux/types.h>
+#include <netlink/msg.h>
+#include <netlink/genl/genl.h>
+#include <wimaxll.h>
+#include "internal.h"
+#define D_LOCAL 0
+#include "debug.h"
+
+
+static
+/**
+ * Lookup the index for a named multicast group
+ *
+ * \param wmx WiMAX device handle
+ * \param name Name of the multicast group to lookup.
+ * \return On success, non-zero positive index for the multicast
+ * group; on error, negative errno code.
+ *
+ * Look up the index of the named multicast group in the cache
+ * obtained at wimaxll_open() time.
+ *
+ * \internal
+ * \ingroup mc_rx
+ * \fn int wimaxll_mc_idx_by_name(struct wimaxll_handle *wmx, const char *name)
+ */
+int wimaxll_mc_idx_by_name(struct wimaxll_handle *wmx, const char *name)
+{
+ unsigned cnt;
+ for (cnt = 0; cnt < WIMAXLL_MC_MAX; cnt++)
+ if (!strcmp(wmx->gnl_mc[cnt].name, name))
+ return cnt;
+ return -EPROTONOSUPPORT;
+}
+
+
+/*
+ * Netlink callback for (disabled) sequence check
+ *
+ * When reading from multicast groups ignore the sequence check, as
+ * they are events (as indicated by the netlink documentation; see the
+ * documentation on nl_disable_sequence_check(), for example here:
+ * http://people.suug.ch/~tgr/libnl/doc-1.1/
+ * group__socket.html#g0ff2f43147e3a4547f7109578b3ca422).
+ *
+ * We need to do this \e manually, as we are using a new callback set
+ * group and thus the libnl defaults set by
+ * nl_disable_sequence_check() don't apply.
+ */
+static
+int wimaxll_seq_check_cb(struct nl_msg *msg, void *arg)
+{
+ return NL_OK;
+}
+
+
+static
+/**
+ * Callback to process a (succesful) message coming from generic
+ * netlink
+ *
+ * \internal
+ *
+ * Called by nl_recvmsgs() when a valid message is received. We
+ * multiplex and handle messages that are known to the library. If the
+ * message is unknown, do nothing other than setting -ENODATA.
+ *
+ * When reading from a pipe with wimaxll_pipe_read(), -ENODATA is
+ * considered a retryable error -- effectively, the message is
+ * skipped.
+ *
+ * \fn int wimaxll_gnl_cb(struct nl_msg *msg, void *_mch)
+ */
+int wimaxll_gnl_cb(struct nl_msg *msg, void *_mch)
+{
+ ssize_t result;
+ struct wimaxll_mc_handle *mch = _mch;
+ struct wimaxll_handle *wmx = mch->wmx;
+ struct nlmsghdr *nl_hdr;
+ struct genlmsghdr *gnl_hdr;
+
+ d_fnstart(7, wmx, "(msg %p mch %p)\n", msg, mch);
+ nl_hdr = nlmsg_hdr(msg);
+ gnl_hdr = nlmsg_data(nl_hdr);
+
+ if (gnl_hdr->cmd >= WIMAX_GNL_OP_MAX)
+ goto error_unknown_msg;
+
+ switch (gnl_hdr->cmd) {
+ case WIMAX_GNL_OP_MSG_TO_USER:
+ if (mch->msg_to_user_cb)
+ result = wimaxll_gnl_handle_msg_to_user(wmx, mch, msg);
+ else
+ goto out_no_handler;
+ break;
+ case WIMAX_GNL_RE_STATE_CHANGE:
+ if (mch->state_change_cb)
+ result = wimaxll_gnl_handle_state_change(wmx, mch, msg);
+ else
+ goto out_no_handler;
+ break;
+ default:
+ goto error_unknown_msg;
+ }
+ wimaxll_mch_maybe_set_result(mch, 0);
+ d_fnend(7, wmx, "(msg %p mch %p) = %zd\n", msg, mch, result);
+ return result;
+
+error_unknown_msg:
+ d_printf(1, wmx, "E: %s: received unknown gnl message %d\n",
+ __func__, gnl_hdr->cmd);
+out_no_handler:
+ wimaxll_mch_maybe_set_result(mch, -ENODATA);
+ result = NL_SKIP;
+ d_fnend(7, wmx, "(msg %p mch %p) = %zd\n", msg, mch, result);
+ return result;
+}
+
+
+/**
+ * Open a handle for reception from a multicast group
+ *
+ * \param wmx WiMAX device handle
+ * \param mc_name Name of the multicast group that has to be opened
+ *
+ * \return If successful, a non-negative handle number (\e { the
+ * multicast group descriptor}), to be given to other functions
+ * for actual operation. In case of error, a negative errno code.
+ *
+ * Allocates a handle to use for reception of data on from a single
+ * multicast group.
+ *
+ * Only one handle may be opened at the same time to each multicast
+ * group.
+ *
+ * \ingroup mc_rx
+ */
+int wimaxll_mc_rx_open(struct wimaxll_handle *wmx,
+ const char *mc_name)
+{
+ int result, idx;
+ struct wimaxll_mc_handle *mch;
+
+ d_fnstart(3, wmx, "(wmx %p mc_name %s)\n", wmx, mc_name);
+ idx = wimaxll_mc_idx_by_name(wmx, mc_name);
+ if (idx < 0) {
+ result = idx;
+ wimaxll_msg(wmx, "E: mc group \"%s\" "
+ "not supported: %d\n", mc_name, result);
+ goto error_mc_idx_by_name;
+ }
+ d_printf(2, wmx, "D: idx is %d\n", idx);
+ result = -EBUSY;
+ if (wmx->gnl_mc[idx].mch) {
+ wimaxll_msg(wmx, "E: BUG! trying to open handle to multicast "
+ "group \"%s\", which is already open\n", mc_name);
+ goto error_reopen;
+ }
+
+ /* Alloc a new multicast group handle */
+ result = -ENOMEM;
+ mch = malloc(sizeof(*mch));
+ if (mch == NULL) {
+ wimaxll_msg(wmx, "E: mc group %s: cannot allocate handle\n",
+ mc_name);
+ goto error_alloc;
+ }
+ memset(mch, 0, sizeof(*mch));
+ mch->wmx = wmx;
+ mch->nlh_rx = nl_handle_alloc();
+ if (mch->nlh_rx == NULL) {
+ result = nl_get_errno();
+ wimaxll_msg(wmx, "E: mc group %s: cannot allocate RX netlink "
+ "handle: %d\n", mc_name, result);
+ goto error_nl_handle_alloc_rx;
+ }
+ result = nl_connect(mch->nlh_rx, NETLINK_GENERIC);
+ if (result < 0) {
+ wimaxll_msg(wmx, "E: mc group %s: cannot connect RX netlink: "
+ "%d\n", mc_name, result);
+ goto error_nl_connect_rx;
+ }
+
+ result = nl_socket_add_membership(mch->nlh_rx, wmx->gnl_mc[idx].id);
+ if (result < 0) {
+ wimaxll_msg(wmx, "E: mc group %s: cannot join multicast group "
+ "%u: %d\n", mc_name, wmx->gnl_mc[idx].id, result);
+ goto error_nl_add_membership;
+ }
+
+ mch->nl_cb = nl_cb_alloc(NL_CB_VERBOSE);
+ if (mch->nl_cb == NULL) {
+ result = -ENOMEM;
+ wimaxll_msg(wmx, "E: mc group %s: cannot allocate callback\n",
+ mc_name);
+ goto error_cb_alloc;
+ }
+
+ nl_cb_set(mch->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,
+ wimaxll_seq_check_cb, NULL);
+ nl_cb_set(mch->nl_cb, NL_CB_VALID, NL_CB_CUSTOM, wimaxll_gnl_cb, mch);
+ nl_cb_err(mch->nl_cb, NL_CB_CUSTOM, wimaxll_gnl_error_cb, mch);
+ wmx->gnl_mc[idx].mch = mch;
+ d_fnend(3, wmx, "(wmx %p mc_name %s) = %d\n", wmx, mc_name, idx);
+ return idx;
+
+error_cb_alloc:
+ /* No need to drop membership, it is removed when we close the
+ * handle */
+error_nl_add_membership:
+ nl_close(mch->nlh_rx);
+error_nl_connect_rx:
+ nl_handle_destroy(mch->nlh_rx);
+error_nl_handle_alloc_rx:
+ free(mch);
+error_alloc:
+error_reopen:
+error_mc_idx_by_name:
+ d_fnend(3, wmx, "(wmx %p mc_name %s) = %d\n", wmx, mc_name, result);
+ return result;
+}
+
+
+/**
+ * Close a multicast group handle
+ *
+ * \param wmx WiMAX handle
+ * \param idx Multicast group handle (as returned by wimaxll_mc_rx_open()).
+ *
+ * Releases resources associated to open multicast group handle.
+ *
+ * \ingroup mc_rx
+ */
+void wimaxll_mc_rx_close(struct wimaxll_handle *wmx, unsigned idx)
+{
+ struct wimaxll_mc_handle *mch;
+ d_fnstart(3, wmx, "(wmx %p idx %u)\n", wmx, idx);
+ if (idx >= WIMAXLL_MC_MAX) {
+ wimaxll_msg(wmx, "E: BUG! multicast group index %u "
+ "higher than allowed maximum %u\n",
+ idx, WIMAXLL_MC_MAX);
+ goto out;
+ }
+ mch = wmx->gnl_mc[idx].mch;
+ wmx->gnl_mc[idx].mch = NULL;
+ nl_cb_put(mch->nl_cb);
+ /* No need to drop handle membership to the msg group, closing
+ * it does it */
+ nl_close(mch->nlh_rx);
+ nl_handle_destroy(mch->nlh_rx);
+ free(mch);
+out:
+ d_fnend(3, wmx, "(wmx %p idx %u) = void\n", wmx, idx);
+}
+
+
+/**
+ * Return the multicast group handle associated to a Pipe ID
+ *
+ * \internal
+ *
+ * \param wmx WiMAX device handle
+ * \param pipe_id Multicast group ID, as returned by
+ * wimaxll_mc_rx_open().
+ * \return file descriptor associated to the multicast group, that can
+ * be fed to functions like select().
+ *
+ * \ingroup mc_rx
+ */
+struct wimaxll_mc_handle *__wimaxll_get_mc_handle(struct wimaxll_handle *wmx,
+ int pipe_id)
+{
+ struct wimaxll_mc_handle *mch = NULL;
+
+ if (pipe_id >= WIMAXLL_MC_MAX) {
+ wimaxll_msg(wmx, "E: BUG! mc group #%u does not exist!\n",
+ pipe_id);
+ goto error;
+ }
+ mch = wmx->gnl_mc[pipe_id].mch;
+ if (mch == NULL) {
+ wimaxll_msg(wmx, "E: BUG! trying to read from non-opened "
+ "mc group #%u\n", pipe_id);
+ goto error;
+ }
+error:
+ return mch;
+}
+
+
+/**
+ * Return the file descriptor associated to a multicast group
+ *
+ * \param wmx WiMAX handle
+ * \param pipe_id Multicast group handle, as returned by
+ * wimaxll_mc_rx_open().
+ * \return file descriptor associated to the multicast group, that can
+ * be fed to functions like select().
+ *
+ * This allows to select() on the file descriptor, which will block
+ * until a message is available, that then can be read with
+ * wimaxll_mc_rx_read().
+ *
+ * \ingroup mc_rx
+ */
+int wimaxll_mc_rx_fd(struct wimaxll_handle *wmx, unsigned pipe_id)
+{
+ int result = -EBADFD;
+ struct wimaxll_mc_handle *mch;
+
+ d_fnstart(3, wmx, "(wmx %p pipe_id %u)\n", wmx, pipe_id);
+ mch = __wimaxll_get_mc_handle(wmx, pipe_id);
+ if (mch != NULL)
+ result = nl_socket_get_fd(mch->nlh_rx);
+ d_fnend(3, wmx, "(wmx %p pipe_id %u) = %zd\n", wmx, pipe_id, result);
+ return result;
+}
+
+
+/**
+ * Read from a multicast group
+ *
+ * \param wmx WiMAX device handle
+ * \param index Multicast group handle, as returned by
+ * wimaxll_mc_rx_open().
+ * \return Value returned by the callback functions (depending on the
+ * implementation of the callback). On error, a negative errno
+ * code:
+ *
+ * -%EINPROGRESS: the message was not received.
+ *
+ * -%ENODATA: messages were received, but none of the known types.
+ *
+ * Read one or more messages from a multicast group and for each valid
+ * one, execute the callbacks set in the multi cast handle.
+ *
+ * The callbacks are expected to handle the messages and set
+ * information in the context specific to the mc handle
+ * (mch->cb_ctx). In case of any type of errors (cb_ctx.result < 0),
+ * it is expected that no resources will be tied to the context.
+ *
+ * \remarks This is a blocking call.
+ *
+ * \ingroup mc_rx
+ *
+ * \internal
+ *
+ * This calls nl_recvmsgs() on the handle specific to a multi-cast
+ * group; wimaxll_gnl_cb() will be called for succesfully received
+ * generic netlink messages from the kernel and execute the callbacks
+ * for each.
+ */
+ssize_t wimaxll_mc_rx_read(struct wimaxll_handle *wmx, unsigned index)
+{
+ ssize_t result;
+ struct wimaxll_mc_handle *mch;
+
+ d_fnstart(3, wmx, "(wmx %p index %u)\n", wmx, index);
+ if (index >= WIMAXLL_MC_MAX) {
+ wimaxll_msg(wmx, "E: BUG! mc group #%u does not exist!\n",
+ index);
+ result = -EINVAL;
+ goto error_bad_index;
+ }
+ mch = wmx->gnl_mc[index].mch;
+ if (mch == NULL) {
+ wimaxll_msg(wmx, "E: BUG! trying to read from non-opened "
+ "mc group #%u\n", index);
+ result = -EBADF;
+ goto error_not_open;
+ }
+
+ /*
+ * The reading and processing happens here
+ *
+ * libnl's nl_recvmsgs() will read and call the different
+ * callbacks we specified at wimaxll_mc_rx_open() time. That's
+ * where the processing of the message content is done.
+ */
+ mch->result = -EINPROGRESS;
+ result = nl_recvmsgs(mch->nlh_rx, mch->nl_cb);
+ if (result < 0) {
+ wimaxll_msg(wmx, "E: %s: nl_recvmgsgs failed: %d\n",
+ __func__, result);
+ goto error_nl_recvmsgs;
+ }
+ result = mch->result;
+ if (result == -EINPROGRESS) {
+ wimaxll_msg(wmx, "E: %s: no messages parsed\n", __func__);
+ goto error_data;
+ }
+ /* No complains on error; the kernel might just be sending an
+ * error out; pass it through. */
+error_data:
+error_nl_recvmsgs:
+error_not_open:
+error_bad_index:
+ d_fnend(3, wmx, "(wmx %p index %u) = %zd\n", wmx, index, result);
+ return result;
+}
+
+void wimax_mc_rx_open() __attribute__ ((weak, alias("wimaxll_mc_rx_open")));
+void wimax_mc_rx_fd() __attribute__ ((weak, alias("wimaxll_mc_rx_fd")));
+void wimax_mc_rx_close() __attribute__ ((weak, alias("wimaxll_mc_rx_close")));
+void wimax_mc_rx_read() __attribute__ ((weak, alias("wimaxll_mc_rx_read")));