summaryrefslogtreecommitdiff
path: root/lib/re-state-change.c
blob: ebd8cb20e5df27afa3f5198e81439180c2b190b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/*
 * Linux WiMax
 * State Change Report
 *
 *
 * 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 state_change_group Tracking state changes
 *
 * When the WiMAX devices change state, the kernel sends \e state \e
 * change notification.
 *
 * An application can simply block a thread waiting for state changes
 * using the following convenience function:
 *
 * @code
 * result = wimaxll_wait_for_state_change(wmx, &old_state, &new_state);
 * @endcode
 *
 * However, in most cases, applications will want to integrate into
 * main loops and use the callback mechanism.
 *
 * For that, they just need to set a callback for the state change
 * notification:
 *
 *
 * @code
 * wimaxll_set_cb_state_change(wmx, my_state_change_callback, context_pointer);
 * @endcode
 *
 * and then wait for notifications to be available (see \ref receiving
 * "receiving with select()"). When data is available and wimax_recv()
 * is called to process it, the callback will be executed for each
 * state change notification.
 *
 * Applications can query the current callback set for the state
 * change notifications with wimaxll_get_cb_state_change().
 */
#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"



/**
 * WIMAX_GNL_RE_STATE_CHANGE: policy specification
 *
 * \internal
 *
 * Authoritative reference for this is at the kernel code,
 * drivers/net/wimax/stack.c.
 */
static
struct nla_policy wimaxll_gnl_re_state_change_policy[WIMAX_GNL_ATTR_MAX + 1] = {
	[WIMAX_GNL_STCH_IFIDX] = { .type = NLA_U32 },
	[WIMAX_GNL_STCH_STATE_OLD] = { .type = NLA_U8 },
	[WIMAX_GNL_STCH_STATE_NEW] = { .type = NLA_U8 },
};


/**
 * Callback to process an WIMAX_GNL_RE_STATE_CHANGE from the kernel
 *
 * \internal
 *
 * \param wmx WiMAX device handle
 * \param mch WiMAX multicast group handle
 * \param msg Pointer to netlink message
 * \return \c enum nl_cb_action
 *
 * wimaxll_mc_rx_read() calls libnl's nl_recvmsgs() to receive messages;
 * when a valid message is received, it goes into a loop that selects
 * a callback to run for each type of message and it will call this
 * function.
 *
 * This just expects a _RE_STATE_CHANGE message, whose payload is what
 * has to be passed to the caller. We just extract the data and call
 * the callback defined in the handle.
 */
int wimaxll_gnl_handle_state_change(struct wimaxll_handle *wmx,
				    struct nl_msg *msg)
{
	ssize_t result;
	struct nlmsghdr *nl_hdr;
	struct genlmsghdr *gnl_hdr;
	struct nlattr *tb[WIMAX_GNL_ATTR_MAX+1];
	enum wimax_st old_state, new_state;
	unsigned dest_ifidx;
	
	d_fnstart(7, wmx, "(wmx %p msg %p)\n", wmx, msg);
	nl_hdr = nlmsg_hdr(msg);
	gnl_hdr = nlmsg_data(nl_hdr);

	assert(gnl_hdr->cmd == WIMAX_GNL_RE_STATE_CHANGE);

	/* Parse the attributes */
	result = genlmsg_parse(nl_hdr, 0, tb, WIMAX_GNL_ATTR_MAX,
			       wimaxll_gnl_re_state_change_policy);
	if (result < 0) {
		wimaxll_msg(wmx, "E: %s: genlmsg_parse() failed: %zd\n",
			  __func__, result);
		goto error_parse;
	}
	/* Find if the message is for the interface wmx represents */
	dest_ifidx = nla_get_u32(tb[WIMAX_GNL_STCH_IFIDX]);
	if (wmx->ifidx > 0 && wmx->ifidx != dest_ifidx) {
		wimaxll_msg(wmx, "E: %s: cannot find IFIDX attribute\n",
			    __func__);
		result = -ENODEV;
		goto error_no_attrs;
	}

	if (tb[WIMAX_GNL_STCH_STATE_OLD] == NULL) {
		wimaxll_msg(wmx, "E: %s: cannot find STCH_STATE_OLD "
			    "attribute\n", __func__);
		result = -ENXIO;
		goto error_no_attrs;

	}
	old_state = nla_get_u8(tb[WIMAX_GNL_STCH_STATE_OLD]);

	if (tb[WIMAX_GNL_STCH_STATE_NEW] == NULL) {
		wimaxll_msg(wmx, "E: %s: cannot find STCH_STATE_NEW "
			    "attribute\n", __func__);
		result = -EINVAL;
		goto error_no_attrs;

	}
	new_state = nla_get_u8(tb[WIMAX_GNL_STCH_STATE_NEW]);

	d_printf(1, wmx, "D: CRX re_state_change old %u new %u\n",
		 old_state, new_state);

	/* If this is an "any" handle, set the wmx->ifidx to the
	 * received one so the callback can now where did the thing
	 * come from. Will be restored.
	 */
	if (wmx->ifidx == 0) {
		wmx->ifidx = dest_ifidx;
		dest_ifidx = 0;
	}
	/* Now execute the callback for handling re-state-change; if
	 * it doesn't update the context's result code, we'll do. */
	result = wmx->state_change_cb(wmx, wmx->state_change_priv,
				      old_state, new_state);
	wmx->ifidx = dest_ifidx;
error_no_attrs:
error_parse:
	d_fnend(7, wmx, "(wmx %p msg %p) = %zd\n", wmx, msg, result);
	return result;
}


/*
 * Context for the default callback we use in
 * wimaxll_wait_for_state_change()
 */
struct wimaxll_state_change_context {
	struct wimaxll_cb_ctx ctx;
	enum wimax_st *old_state, *new_state;
	int set:1;
};


/**
 * Get the callback and priv pointer for a WIMAX_GNL_RE_STATE_CHANGE message
 *
 * \param wmx WiMAX handle.
 * \param cb Where to store the current callback function.
 * \param priv Where to store the private data pointer passed to the
 *     callback.
 *
 * \ingroup state_change_group
 */
void wimaxll_get_cb_state_change(struct wimaxll_handle *wmx,
			       wimaxll_state_change_cb_f *cb, void **priv)
{
	*cb = wmx->state_change_cb;
	*priv = wmx->state_change_priv;
}


/**
 * Set the callback and priv pointer for a WIMAX_GNL_RE_STATE_CHANGE message
 *
 * \param wmx WiMAX handle.
 * \param cb Callback function to set
 * \param priv Private data pointer to pass to the callback function.
 *
 * \ingroup state_change_group
 */
void wimaxll_set_cb_state_change(struct wimaxll_handle *wmx,
			       wimaxll_state_change_cb_f cb, void *priv)
{
	wmx->state_change_cb = cb;
	wmx->state_change_priv = priv;
}


/*
 * Default callback we use in wimaxll_wait_for_state_change()
 */
static
int wimaxll_cb_state_change(struct wimaxll_handle *wmx, void *_ctx,
			    enum wimax_st old_state, enum wimax_st new_state)
{
	struct wimaxll_cb_ctx *ctx = _ctx;
	struct wimaxll_state_change_context *stch_ctx =
		wimaxll_container_of(ctx, struct wimaxll_state_change_context,
				     ctx);

	if (stch_ctx->set)
		return -EBUSY;
	*stch_ctx->old_state = old_state;
	*stch_ctx->new_state = new_state;
	stch_ctx->set = 1;
	return -EBUSY;
}


/**
 * Wait for an state change notification from the kernel
 *
 * \param wmx WiMAX device handle
 * \param old_state Pointer to where to store the previous state
 * \param new_state Pointer to where to store the new state
 * \return If successful, 0 and the values pointed to by the \a
 *     old_state and \a new_state arguments are valid; on error, a
 *     negative \a errno code and the state pointers contain no valid
 *     information.
 *
 * Waits for the WiMAX device to change state and reports said state
 * change.
 *
 * Internally, this function uses wimax_recv() , which means that on
 * reception (from the kernel) of notifications other than state
 * change, any callbacks that are set for them will be executed.
 *
 * \note This is a blocking call.
 *
 * \note This function cannot be run in parallel with other code that
 *     modifies the \e state \e change callbacks for this same handle.
 *
 * \ingroup state_change_group
 */
ssize_t wimaxll_wait_for_state_change(struct wimaxll_handle *wmx,
				      enum wimax_st *old_state,
				      enum wimax_st *new_state)
{
	ssize_t result;
	wimaxll_state_change_cb_f prev_cb = NULL;
	void *prev_priv = NULL;
	struct wimaxll_state_change_context ctx = {
		.ctx = WIMAXLL_CB_CTX_INIT(wmx),
		.old_state = old_state,
		.new_state = new_state,
		.set = 0,
	};

	d_fnstart(3, wmx, "(wmx %p old_state %p new_state %p)\n",
		  wmx, old_state, new_state);
	wimaxll_get_cb_state_change(wmx, &prev_cb, &prev_priv);
	wimaxll_set_cb_state_change(wmx, wimaxll_cb_state_change, &ctx.ctx);
	result = wimaxll_recv(wmx);
	/* the callback filled out *old_state and *new_state if ok */
	wimaxll_set_cb_state_change(wmx, prev_cb, prev_priv);
	d_fnend(3, wmx, "(wmx %p old_state %p [%u] new_state %p [%u])\n",
		wmx, old_state, *old_state, new_state, *new_state);
	return result;
}