fpi-ssm.c 8.84 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * Functions to assist with asynchronous driver <---> library communications
 * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
 *
 * This library 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.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

20 21
#define FP_COMPONENT "drv"

22
#include "fp_internal.h"
23
#include "fpi-ssm.h"
24

25
#include <config.h>
26 27
#include <errno.h>

28 29 30
/**
 * SECTION:fpi-ssm
 * @title: Sequential state machine
31
 * @short_description: State machine helpers
32
 *
33 34
 * Asynchronous driver design encourages some kind of state machine behind it.
 * In most cases, the state machine is entirely linear - you only go to the
35
 * next state, you never jump or go backwards. The #fpi_ssm functions help you
36 37
 * implement such a machine.
 *
38 39 40
 * e.g. `S1` ↦ `S2` ↦ `S3` ↦ `S4`
 *
 * `S1` is the start state
41 42 43
 * There is also an implicit error state and an implicit accepting state
 * (both with implicit edges from every state).
 *
44 45
 * You can also jump to any arbitrary state (while marking completion of the
 * current state) while the machine is running. In other words there are
46
 * implicit edges linking one state to every other state.
47
 *
48 49 50
 * To create an #fpi_ssm, you pass a state handler function and the total number of
 * states (4 in the above example) to fpi_ssm_new(). Note that the state numbers
 * start at zero, making them match the first value in a C enumeration.
51
 *
52 53
 * To start a ssm, you pass in a completion callback function to fpi_ssm_start()
 * which gets called when the ssm completes (both on error and on failure).
54 55 56 57 58 59 60 61
 *
 * To iterate to the next state, call fpi_ssm_next_state(). It is legal to
 * attempt to iterate beyond the final state - this is equivalent to marking
 * the ssm as successfully completed.
 *
 * To mark successful completion of a SSM, either iterate beyond the final
 * state or call fpi_ssm_mark_completed() from any state.
 *
62
 * To mark failed completion of a SSM, call fpi_ssm_mark_failed() from any
63 64
 * state. You must pass a non-zero error code.
 *
65 66 67 68 69 70 71
 * Your state handling function looks at the return value of
 * fpi_ssm_get_cur_state() in order to determine the current state and hence
 * which operations to perform (a switch statement is appropriate).
 *
 * Typically, the state handling function fires off an asynchronous
 * communication with the device (such as a libsub transfer), and the
 * callback function iterates the machine to the next state
72
 * upon success (or fails).
73
 *
74 75 76
 * Your completion callback should examine the return value of
 * fpi_ssm_get_error() in order to determine whether the #fpi_ssm completed or
 * failed. An error code of zero indicates successful completion.
77 78
 */

79 80
struct fpi_ssm {
	struct fp_dev *dev;
Bastien Nocera's avatar
Bastien Nocera committed
81
	fpi_ssm *parentsm;
82
	void *user_data;
83 84 85 86 87 88 89 90
	int nr_states;
	int cur_state;
	gboolean completed;
	int error;
	ssm_completed_fn callback;
	ssm_handler_fn handler;
};

91 92 93 94 95
/**
 * fpi_ssm_new:
 * @dev: a #fp_dev fingerprint device
 * @handler: the callback function
 * @nr_states: the number of states
96
 * @user_data: the user data to pass to callbacks
97 98 99 100 101 102
 *
 * Allocate a new ssm, with @nr_states states. The @handler callback
 * will be called after each state transition.
 *
 * Returns: a new #fpi_ssm state machine
 */
103 104 105 106
fpi_ssm *fpi_ssm_new(struct fp_dev  *dev,
		     ssm_handler_fn  handler,
		     int             nr_states,
		     void           *user_data)
107
{
Bastien Nocera's avatar
Bastien Nocera committed
108
	fpi_ssm *machine;
109
	BUG_ON(nr_states < 1);
110 111 112 113 114 115

	machine = g_malloc0(sizeof(*machine));
	machine->handler = handler;
	machine->nr_states = nr_states;
	machine->dev = dev;
	machine->completed = TRUE;
116
	machine->user_data = user_data;
117 118 119
	return machine;
}

120 121 122 123
/**
 * fpi_ssm_get_user_data:
 * @machine: an #fpi_ssm state machine
 *
124 125
 * Retrieve the pointer to user data set when fpi_ssm_new()
 * is called.
126 127 128
 *
 * Returns: a pointer
 */
129
void *
Bastien Nocera's avatar
Bastien Nocera committed
130
fpi_ssm_get_user_data(fpi_ssm *machine)
131
{
132
	return machine->user_data;
133 134
}

135 136 137 138 139 140 141
/**
 * fpi_ssm_free:
 * @machine: an #fpi_ssm state machine
 *
 * Frees a state machine. This does not call any error or success
 * callbacks, so you need to do this yourself.
 */
Bastien Nocera's avatar
Bastien Nocera committed
142
void fpi_ssm_free(fpi_ssm *machine)
143 144 145 146 147 148 149
{
	if (!machine)
		return;
	g_free(machine);
}

/* Invoke the state handler */
Bastien Nocera's avatar
Bastien Nocera committed
150
static void __ssm_call_handler(fpi_ssm *machine)
151
{
152
	fp_dbg("%p entering state %d", machine, machine->cur_state);
153
	machine->handler(machine, machine->dev, machine->user_data);
154 155
}

156 157 158 159 160 161
/**
 * fpi_ssm_start:
 * @ssm: an #fpi_ssm state machine
 * @callback: the #ssm_completed_fn callback to call on completion
 *
 * Starts a state machine. You can also use this function to restart
162
 * a completed or failed state machine. The @callback will be called
163 164
 * on completion.
 */
Bastien Nocera's avatar
Bastien Nocera committed
165
void fpi_ssm_start(fpi_ssm *ssm, ssm_completed_fn callback)
166 167 168 169 170 171 172 173 174
{
	BUG_ON(!ssm->completed);
	ssm->callback = callback;
	ssm->cur_state = 0;
	ssm->completed = FALSE;
	ssm->error = 0;
	__ssm_call_handler(ssm);
}

175
static void __subsm_complete(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
176
{
Bastien Nocera's avatar
Bastien Nocera committed
177
	fpi_ssm *parent = ssm->parentsm;
178 179
	BUG_ON(!parent);
	if (ssm->error)
180
		fpi_ssm_mark_failed(parent, ssm->error);
181 182 183 184 185
	else
		fpi_ssm_next_state(parent);
	fpi_ssm_free(ssm);
}

186 187 188 189 190 191 192
/**
 * fpi_ssm_start_subsm:
 * @parent: an #fpi_ssm state machine
 * @child: an #fpi_ssm state machine
 *
 * Starts a state machine as a child of another. if the child completes
 * successfully, the parent will be advanced to the next state. if the
193
 * child fails, the parent will be marked as failed with the same error code.
194
 *
195
 * The child will be automatically freed upon completion or failure.
196
 */
Bastien Nocera's avatar
Bastien Nocera committed
197
void fpi_ssm_start_subsm(fpi_ssm *parent, fpi_ssm *child)
198 199 200 201 202
{
	child->parentsm = parent;
	fpi_ssm_start(child, __subsm_complete);
}

203 204 205 206 207 208 209
/**
 * fpi_ssm_mark_completed:
 * @machine: an #fpi_ssm state machine
 *
 * Mark a ssm as completed successfully. The callback set when creating
 * the state machine with fpi_ssm_new() will be called synchronously.
 */
Bastien Nocera's avatar
Bastien Nocera committed
210
void fpi_ssm_mark_completed(fpi_ssm *machine)
211 212 213
{
	BUG_ON(machine->completed);
	machine->completed = TRUE;
214
	fp_dbg("%p completed with status %d", machine, machine->error);
215
	if (machine->callback)
216
		machine->callback(machine, machine->dev, machine->user_data);
217 218
}

219
/**
220
 * fpi_ssm_mark_failed:
221 222 223
 * @machine: an #fpi_ssm state machine
 * @error: the error code
 *
224
 * Mark a state machine as failed with @error as the error code.
225
 */
226
void fpi_ssm_mark_failed(fpi_ssm *machine, int error)
227
{
228
	fp_dbg("error %d from state %d", error, machine->cur_state);
229 230 231 232 233
	BUG_ON(error == 0);
	machine->error = error;
	fpi_ssm_mark_completed(machine);
}

234 235 236 237 238 239 240 241
/**
 * fpi_ssm_next_state:
 * @machine: an #fpi_ssm state machine
 *
 * Iterate to next state of a state machine. If the current state is the
 * last state, then the state machine will be marked as completed, as
 * if calling fpi_ssm_mark_completed().
 */
Bastien Nocera's avatar
Bastien Nocera committed
242
void fpi_ssm_next_state(fpi_ssm *machine)
243
{
244 245
	g_return_if_fail (machine != NULL);

246 247 248 249 250 251 252 253 254
	BUG_ON(machine->completed);
	machine->cur_state++;
	if (machine->cur_state == machine->nr_states) {
		fpi_ssm_mark_completed(machine);
	} else {
		__ssm_call_handler(machine);
	}
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
/**
 * fpi_ssm_next_state_timeout_cb:
 * @dev: a struct #fp_dev
 * @data: a pointer to an #fpi_ssm state machine
 *
 * Same as fpi_ssm_next_state(), but to be used as a callback
 * for an fpi_timeout_add() callback, when the state change needs
 * to happen after a timeout.
 *
 * Make sure to pass the #fpi_ssm as the `user_data` argument
 * for that fpi_timeout_add() call.
 */
void
fpi_ssm_next_state_timeout_cb(struct fp_dev *dev,
			      void          *data)
{
	g_return_if_fail (dev != NULL);
	g_return_if_fail (data != NULL);

	fpi_ssm_next_state(data);
}

277 278 279 280 281
/**
 * fpi_ssm_jump_to_state:
 * @machine: an #fpi_ssm state machine
 * @state: the state to jump to
 *
282
 * Jump to the @state state, bypassing intermediary states.
283
 */
Bastien Nocera's avatar
Bastien Nocera committed
284
void fpi_ssm_jump_to_state(fpi_ssm *machine, int state)
285 286 287 288 289 290 291
{
	BUG_ON(machine->completed);
	BUG_ON(state >= machine->nr_states);
	machine->cur_state = state;
	__ssm_call_handler(machine);
}

292 293 294 295 296 297 298 299 300
/**
 * fpi_ssm_get_cur_state:
 * @machine: an #fpi_ssm state machine
 *
 * Returns the value of the current state. Note that states are
 * 0-indexed, so a value of 0 means “the first state”.
 *
 * Returns: the current state.
 */
Bastien Nocera's avatar
Bastien Nocera committed
301
int fpi_ssm_get_cur_state(fpi_ssm *machine)
302 303 304 305
{
	return machine->cur_state;
}

306 307 308 309
/**
 * fpi_ssm_get_error:
 * @machine: an #fpi_ssm state machine
 *
310
 * Returns the error code set by fpi_ssm_mark_failed().
311 312 313
 *
 * Returns: a error code
 */
Bastien Nocera's avatar
Bastien Nocera committed
314
int fpi_ssm_get_error(fpi_ssm *machine)
315 316 317
{
	return machine->error;
}