diff options
Diffstat (limited to 'lib/op-open.c')
-rw-r--r-- | lib/op-open.c | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/lib/op-open.c b/lib/op-open.c new file mode 100644 index 0000000..d151284 --- /dev/null +++ b/lib/op-open.c @@ -0,0 +1,548 @@ +/* + * Linux WiMAX + * Opening handles + * + * + * 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 device_management WiMAX device management + * + * The main device management operations are wimaxll_open(), + * wimaxll_close() and wimax_reset(). + * + * It is allowed to have more than one handle opened at the same + * time. + * + * Use wimaxll_ifname() to obtain the name of the WiMAX interface a + * handle is open for. + * + * \internal + * \section Roadmap Roadmap + * + * \code + * wimaxll_open() + * __wimaxll_cmd_open() + * nl_recvmsgs() + * wimaxll_gnl_rp_ifinfo_cb() + * __wimaxll_ifinfo_parse_groups() + * wimaxll_gnl_error_cb() + * + * wimaxll_close() + * wimaxll_mc_rx_close() + * wimaxll_free() + * + * wimaxll_ifname() + * \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/netlink.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/ctrl.h> +#include <wimaxll.h> +#include "internal.h" +#define D_LOCAL 0 +#include "debug.h" + + +/** + * Generic Netlink: IFINFO message policy + * + * \internal + * \ingroup device_management + * + * Authoritative reference for this is in drivers/net/wimax/op-open.c; + * this is just a copy. + */ +static +struct nla_policy wimaxll_gnl_ifinfo_policy[WIMAX_GNL_ATTR_MAX + 1] = { + [WIMAX_GNL_IFINFO_MC_GROUPS] = { .type = NLA_NESTED }, + [WIMAX_GNL_IFINFO_MC_GROUP] = { .type = NLA_NESTED }, + [WIMAX_GNL_IFINFO_MC_NAME] = { + .type = NLA_STRING, + .maxlen = GENL_NAMSIZ + }, + [WIMAX_GNL_IFINFO_MC_ID] = { .type = NLA_U16 }, +}; + + +/** + * Callback context for processing IFINFO messages + * + * \ingroup device_management + * \internal + * + * All the callbacks store the information they parse from the + * messages here; we do it so that only once everything passes with + * flying colors, then we commit the information the kernel sent. + * + * We include a \a struct wimaxll_gnl_cb_context so we can share this + * same data structure with the error handler and not require to check + * different data in different places. + * + * gnl_mc stands for Generic Netlink MultiCast group + */ +struct wimaxll_ifinfo_context { + struct wimaxll_gnl_cb_context gnl; + struct wimaxll_mc_group gnl_mc[WIMAXLL_MC_MAX]; +}; + + +/** + * Parse the list of multicast groups sent as part of a IFINFO reply + * + * \ingroup device_management + * \internal + * + * \param ctx IFINFO context, containing the WiMAX handle + * + * \param nla_groups Netlink attribute (type + * WIMAXLL_GNL_IFINFO_MC_GROUPS) that contains the list of (nested) + * attributes (type WIMAXLL_GNL_IFINFO_MC_GROUP). + * Just pass tb[WIMAXLL_GNL_IFINFO_MC_GROUPS]; this function will + * check for it to be ok (non-NULL). + * + * \return 0 if ok, < 0 errno code on error. + * + * On success, the \a wmx->mc_group array will be cleared and + * overwritten with the list of reported groups (and their + * IDs). + * + * On error, nothing is modified. + * + * The kernel has sent a list of attributes: a nested attribute + * (_MC_GROUPS) containing a list of nested attributes (_MC_GROUP) + * each with a _MC_NAME and _MC_ID attribute]. + * + * We need to parse it and extract the multicast group data. At least + * a multicast group ('reports') has to exist; the rest are optional + * to a maximum of WIMAXLL_MC_MAX (without counting 'reports'). + * + * \note The private data for this callback is not an \a struct + * wimaxll_mc_handle as most of the other ones! + */ +static +int __wimaxll_ifinfo_parse_groups(struct wimaxll_ifinfo_context *ctx, + struct nlattr *nla_groups) +{ + int result, remaining, cnt = 0; + struct wimaxll_handle *wmx = ctx->gnl.wmx; + struct nlattr *nla_group; + + d_fnstart(7, wmx, "(ctx %p [wmx %p] nla_groups %p)\n", + ctx, wmx, nla_groups); + if (nla_groups == NULL) { + wimaxll_msg(wmx, "E: %s: the kernel didn't send a " + "WIMAXLL_GNL_IFINFO_MC_GROUPS attribute\n", + __func__); + result = -EBADR; + goto error_no_groups; + } + + memset(ctx->gnl_mc, 0, sizeof(ctx->gnl_mc)); + nla_for_each_nested(nla_group, nla_groups, remaining) { + char *name; + int id; + struct nlattr *tb[WIMAX_GNL_ATTR_MAX+1]; + + d_printf(8, wmx, "D: group %d, remaining %d\n", + cnt, remaining); + result = nla_parse_nested(tb, WIMAX_GNL_ATTR_MAX, + nla_group, + wimaxll_gnl_ifinfo_policy); + if (result < 0) { + wimaxll_msg(wmx, "E: %s: can't parse " + "WIMAX_GNL_MC_GROUP attribute: %d\n", + __func__, result); + continue; + } + + if (tb[WIMAX_GNL_IFINFO_MC_NAME] == NULL) { + wimaxll_msg(wmx, "E: %s: multicast group missing " + "WIMAX_GNL_IFINFO_MC_NAME attribute\n", + __func__); + continue; + } + name = nla_get_string(tb[WIMAX_GNL_IFINFO_MC_NAME]); + + if (tb[WIMAX_GNL_IFINFO_MC_ID] == NULL) { + wimaxll_msg(wmx, "E: %s: multicast group missing " + "WIMAX_GNL_IFINFO_MC_ID attribute\n", + __func__); + continue; + } + id = nla_get_u16(tb[WIMAX_GNL_IFINFO_MC_ID]); + + d_printf(6, wmx, "D: MC group %s:%d\n", name, id); + strncpy(ctx->gnl_mc[cnt].name, name, + sizeof(ctx->gnl_mc[cnt].name)); + ctx->gnl_mc[cnt].id = id; + cnt++; + } + result = 0; +error_no_groups: + d_fnend(7, wmx, "(ctx %p [wmx %p] nla_groups %p) = %d\n", + ctx, wmx, nla_groups, result); + return result; +} + + +/* + * Same as wimaxll_gnl_error_cb(), but takes a different type of + * context, so, need another one (fitting an mch in there made little + * sense). + */ +int wimaxll_gnl_rp_ifinfo_error_cb(struct sockaddr_nl *nla, + struct nlmsgerr *nlerr, + void *_ctx) +{ + struct wimaxll_ifinfo_context *ctx = _ctx; + struct wimaxll_handle *wmx = ctx->gnl.wmx; + + d_fnstart(7, wmx, "(nla %p nlnerr %p [%d] ctx %p)\n", + nla, nlerr, nlerr->error, _ctx); + if (ctx->gnl.result == -EINPROGRESS) + ctx->gnl.result = nlerr->error; + d_fnend(7, wmx, "(nla %p nlnerr %p [%d] ctx %p) = %d\n", + nla, nlerr, nlerr->error, _ctx, NL_STOP); + return NL_STOP; +} + + +/** + * Seek for and process a WIMAX_GNL_RP_IFINFO message from the kernel + * + * \ingroup device_management + * \internal + * + * \param msg Netlink message containing the reply + * \param _ctx Pointer to a \a struct wimaxll_gnl_cb_context where the + * context will be returned. + * + * \return 'enum nl_cb_action', NL_OK if there is no error, NL_STOP on + * error and _ctx possibly updated. + * + * This will take a received netlink message, check it is a \a + * WIMAX_GNL_RP_IFINFO, parse the contents and store them in an + * struct wimaxll_ifinfo_context (for the caller to use later on). + */ +static +int wimaxll_gnl_rp_ifinfo_cb(struct nl_msg *msg, void *_ctx) +{ + int result; + struct wimaxll_ifinfo_context *ctx = _ctx; + struct wimaxll_handle *wmx = ctx->gnl.wmx; + struct nlmsghdr *nl_hdr; + struct genlmsghdr *genl_hdr; + struct nlattr *tb[WIMAX_GNL_ATTR_MAX + 1]; + unsigned major, minor; + + d_fnstart(7, wmx, "(msg %p ctx %p)\n", msg, ctx); + nl_hdr = nlmsg_hdr(msg); + genl_hdr = nlmsg_data(nl_hdr); + + if (genl_hdr->cmd != WIMAX_GNL_RP_IFINFO) { + ctx->gnl.result = -ENXIO; + result = NL_SKIP; + d_printf(1, wmx, "D: ignoring unknown reply %d\n", + genl_hdr->cmd); + goto error_parse; + } + + /* Check version compatibility -- check include/linux/wimax.h + * for a complete description. The idea is to allow for good + * expandability of the interface without causing breakage. */ + major = genl_hdr->version / 10; + minor = genl_hdr->version % 10; + if (major != WIMAX_GNL_VERSION / 10) { + ctx->gnl.result = -EBADR; + result = NL_SKIP; + wimaxll_msg(wmx, "E: kernel's major WiMAX GNL interface " + "version (%d) is different that supported %d; " + "aborting\n", major, WIMAX_GNL_VERSION / 10); + goto error_bad_major; + } + if (minor < WIMAX_GNL_VERSION % 10) + wimaxll_msg(wmx, "W: kernel's minor WiMAX GNL interface " + "version (%d) is lower that supported %d; things " + "might not work\n", minor, WIMAX_GNL_VERSION % 10); + + /* Parse the attributes */ + result = genlmsg_parse(nl_hdr, 0, tb, WIMAX_GNL_IFINFO_MAX, + wimaxll_gnl_ifinfo_policy); + if (result < 0) { + wimaxll_msg(wmx, "E: %s: genlmsg_parse() failed: %d\n", + __func__, result); + ctx->gnl.result = result; + result = NL_SKIP; + goto error_parse; + } + result = __wimaxll_ifinfo_parse_groups(ctx, + tb[WIMAX_GNL_IFINFO_MC_GROUPS]); + if (result < 0) { + ctx->gnl.result = result; + result = NL_SKIP; + } else { + ctx->gnl.result = 0; + result = NL_OK; + } +error_parse: +error_bad_major: + d_fnend(7, wmx, "(msg %p ctx %p) = %d\n", msg, ctx, result); + return result; +} + + +static +int __wimaxll_cmd_open(struct wimaxll_handle *wmx) +{ + int result; + struct nl_msg *msg; + struct nl_cb *cb; + struct wimaxll_ifinfo_context ctx; + + d_fnstart(5, wmx, "(wmx %p)\n", wmx); + msg = nlmsg_new(); + if (msg == NULL) { + result = -errno; + wimaxll_msg(wmx, "E: %s: cannot allocate generic netlink " + "message: %m\n", __func__); + goto error_msg_alloc; + } + if (genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, + wimaxll_family_id(wmx), 0, 0, + WIMAX_GNL_OP_OPEN, WIMAX_GNL_VERSION) == NULL) { + result = -errno; + wimaxll_msg(wmx, "E: %s: error preparing message: %m\n", + __func__); + goto error_msg_prep; + } + result = nl_send_auto_complete(wmx->nlh_tx, msg); + if (result < 0) { + wimaxll_msg(wmx, "E: %s: error sending message: %d\n", + __func__, result); + goto error_msg_send; + } + + /* Read the reply, that includes a WIMAX_GNL_RP_IFINFO message + * + * We need to set the call back error handler, so we get the + * default cb handler and modify it. + */ + ctx.gnl.wmx = wmx; + ctx.gnl.result = -EINPROGRESS; + cb = nl_socket_get_cb(wmx->nlh_tx); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, + wimaxll_gnl_rp_ifinfo_cb, &ctx); + nl_cb_err(cb, NL_CB_CUSTOM, wimaxll_gnl_rp_ifinfo_error_cb, &ctx.gnl); + nl_recvmsgs_default(wmx->nlh_tx); + nl_cb_put(cb); + result = ctx.gnl.result; + if (result >= 0) + nl_wait_for_ack(wmx->nlh_tx); + if (result == -EINPROGRESS) + wimaxll_msg(wmx, "E: %s: the kernel didn't reply with a " + "WIMAX_GNL_RP_IFINFO message\n", __func__); + d_printf(1, wmx, "D: processing result is %d\n", result); + + /* All fine and dandy, commit the data */ + memcpy(wmx->gnl_mc, ctx.gnl_mc, sizeof(ctx.gnl_mc)); +error_msg_prep: +error_msg_send: + nlmsg_free(msg); +error_msg_alloc: + d_fnend(5, wmx, "(wmx %p) = %d\n", wmx, result); + return result; +} + + +static +void wimaxll_free(struct wimaxll_handle *wmx) +{ + free(wmx); +} + + +/** + * Open a handle to the WiMAX control interface in the kernel + * + * \param device device name of the WiMAX network interface + * \return WiMAX device handle on success; on error, %NULL is returned + * and the \a errno variable is updated with a corresponding + * negative value. + * + * When opening the handle to the device, a basic check of API + * versioning will be done. If the kernel interface has a different + * major version, the \c wimaxll_open() call will fail (existing + * interfaces modified or removed). A higher kernel minor version is + * allowed (new interfaces added); a lower kernel minor version is not + * (the library needs interfaces that are not in the kernel). + * + * \ingroup device_management + * \internal + * + * Allocates the netlink handles needed to talk to the kernel. With + * that, looks up the Generic Netlink Family ID associated (if none, + * it's not a WiMAX device). This is done by querying generic netlink + * for a family called "WiMAX <DEVNAME>". + * + * An open command is issued to get and process interface information + * (like multicast group mappings, etc). This also does an interface + * versioning verification. + * + * The bulk of this code is in the parsing of the generic netlink + * reply that the \a WIMAX_GNL_OP_OPEN command returns (\a + * WIMAX_GNL_RP_IFINFO) with information about the WiMAX control + * interface. + * + * We process said reply using the \e libnl callback mechanism, + * invoked by __wimaxll_cmd_open(). All the information is stored in a + * struct wimaxll_ifinfo_context by the callbacks. When the callback + * (and thus message) processing finishes, __wimaxll_cmd_open(), if all + * successful, will commit the information from the context to the + * handle. On error, nothing is modified. + * + * \note Because events are going to ben processed, sequence checks + * have to be disabled (as indicated by the generic netlink + * documentation). + */ +struct wimaxll_handle *wimaxll_open(const char *device) +{ + int result; + struct wimaxll_handle *wmx; + char buf[64]; + + d_fnstart(3, NULL, "(device %s)\n", device); + result = ENOMEM; + wmx = malloc(sizeof(*wmx)); + if (wmx == NULL) { + wimaxll_msg(NULL, "E: cannot allocate WiMax handle: %m\n"); + goto error_gnl_handle_alloc; + } + memset(wmx, 0, sizeof(*wmx)); + strncpy(wmx->name, device, sizeof(wmx->name)); + + /* Setup the TX side */ + wmx->nlh_tx = nl_handle_alloc(); + if (wmx->nlh_tx == NULL) { + result = nl_get_errno(); + wimaxll_msg(wmx, "E: cannot open TX netlink handle: %d\n", + result); + goto error_nl_handle_alloc_tx; + } + result = nl_connect(wmx->nlh_tx, NETLINK_GENERIC); + if (result < 0) { + wimaxll_msg(wmx, "E: cannot connect TX netlink: %d\n", result); + goto error_nl_connect_tx; + } + + /* Lookup the generic netlink family */ + snprintf(buf, sizeof(buf), "WiMAX %s", wmx->name); + result = genl_ctrl_resolve(wmx->nlh_tx, buf); + if (result < 0) { + wimaxll_msg(wmx, "E: device %s presents no WiMAX interface; " + "it might not exist, not be be a WiMAX device or " + "support an interface unknown to libwimax: %d\n", + wmx->name, result); + goto error_ctrl_resolve; + } + wmx->gnl_family_id = result; + d_printf(1, wmx, "D: WiMAX device %s, genl family ID %d\n", + wmx->name, wmx->gnl_family_id); + + result = __wimaxll_cmd_open(wmx); /* Get interface information */ + if (result < 0) + goto error_cmd_open; + + result = wimaxll_mc_rx_open(wmx, "msg"); + if (result == -EPROTONOSUPPORT) /* not open? */ + wmx->mc_msg = WIMAXLL_MC_MAX; /* for wimaxll_mc_rx_read() */ + else if (result < 0) { + wimaxll_msg(wmx, "E: cannot open 'msg' multicast group: " + "%d\n", result); + goto error_msg_open; + } else + wmx->mc_msg = result; + d_fnend(3, wmx, "(device %s) = %p\n", device, wmx); + return wmx; + + wimaxll_mc_rx_close(wmx, wmx->mc_msg); +error_msg_open: +error_cmd_open: +error_ctrl_resolve: + nl_close(wmx->nlh_tx); +error_nl_connect_tx: + nl_handle_destroy(wmx->nlh_tx); +error_nl_handle_alloc_tx: + wimaxll_free(wmx); +error_gnl_handle_alloc: + errno = -result; + d_fnend(3, NULL, "(device %s) = NULL\n", device); + return NULL; +} + + +/** + * Close a device handle opened with wimaxll_open() + * + * \param wmx WiMAX device handle + * + * \ingroup device_management + * \internal + * + * Performs the natural oposite actions done in wimaxll_open(). All + * generic netlink multicast groups are destroyed, the netlink handle + * is closed and destroyed and finally, the actual handle is released. + */ +void wimaxll_close(struct wimaxll_handle *wmx) +{ + unsigned cnt; + + d_fnstart(3, NULL, "(wmx %p)\n", wmx); + for (cnt = 0; cnt < WIMAXLL_MC_MAX; cnt++) + if (wmx->gnl_mc[cnt].mch) + wimaxll_mc_rx_close(wmx, cnt); + nl_close(wmx->nlh_tx); + nl_handle_destroy(wmx->nlh_tx); + wimaxll_free(wmx); + d_fnend(3, NULL, "(wmx %p) = void\n", wmx); +} + +void wimax_open() __attribute__ ((weak, alias("wimaxll_open"))); +void wimax_close() __attribute__ ((weak, alias("wimaxll_close"))); |