From aeeec97fb2efafdc22d55e505e28c41ff9d117ed Mon Sep 17 00:00:00 2001
From: Daniel Drake <dsd@cs.manchester.ac.uk>
Date: Mon, 4 Feb 2008 10:24:27 +0000
Subject: [PATCH] upekts: port to asynchronous model

---
 libfprint/Makefile.am      |    5 +-
 libfprint/core.c           |    1 +
 libfprint/drivers/upekts.c | 1555 ++++++++++++++++++++++++------------
 3 files changed, 1069 insertions(+), 492 deletions(-)

diff --git a/libfprint/Makefile.am b/libfprint/Makefile.am
index 88c3cf3..2a8f08e 100644
--- a/libfprint/Makefile.am
+++ b/libfprint/Makefile.am
@@ -8,8 +8,8 @@ AES2501_SRC = drivers/aes2501.c drivers/aes2501.h
 AES4000_SRC = drivers/aes4000.c
 FDU2000_SRC = drivers/fdu2000.c
 
-#DRIVER_SRC = $(UPEKTS_SRC) $(URU4000_SRC) $(AES1610_SRC) $(AES2501_SRC) $(AES4000_SRC) $(UPEKTC_SRC) $(FDU2000_SRC)
-DRIVER_SRC =
+DRIVER_SRC = $(UPEKTS_SRC)
+#DRIVER_SRC = $(URU4000_SRC) $(AES1610_SRC) $(AES2501_SRC) $(AES4000_SRC) $(UPEKTC_SRC) $(FDU2000_SRC)
 
 NBIS_SRC = \
 	nbis/include/bozorth.h \
@@ -56,6 +56,7 @@ libfprint_la_SOURCES =	\
 	fp_internal.h	\
 	core.c		\
 	data.c		\
+	drv.c		\
 	img.c		\
 	imgdev.c	\
 	aeslib.c	\
diff --git a/libfprint/core.c b/libfprint/core.c
index fdaa519..a7741ad 100644
--- a/libfprint/core.c
+++ b/libfprint/core.c
@@ -325,6 +325,7 @@ static void register_driver(struct fp_driver *drv)
 }
 
 static struct fp_driver * const primitive_drivers[] = {
+	&upekts_driver,
 };
 
 static struct fp_img_driver * const img_drivers[] = {
diff --git a/libfprint/drivers/upekts.c b/libfprint/drivers/upekts.c
index caf5f6f..c63f779 100644
--- a/libfprint/drivers/upekts.c
+++ b/libfprint/drivers/upekts.c
@@ -1,6 +1,6 @@
 /*
  * UPEK TouchStrip driver for libfprint
- * Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
+ * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
  *
  * Based in part on libthinkfinger:
  * Copyright (C) 2006-2007 Timo Hoenig <thoenig@suse.de>
@@ -39,8 +39,14 @@
 #define EP_OUT (2 | LIBUSB_ENDPOINT_OUT)
 #define TIMEOUT 5000
 
+#define MSG_READ_BUF_SIZE 0x40
+#define MAX_DATA_IN_READ_BUF (MSG_READ_BUF_SIZE - 9)
+
 struct upekts_dev {
-	uint8_t seq;
+	gboolean enroll_passed;
+	gboolean first_verify_iteration;
+	gboolean stop_verify;
+	uint8_t seq; /* FIXME: improve/automate seq handling */
 };
 
 static const uint16_t crc_table[256] = {
@@ -127,11 +133,10 @@ static uint16_t udf_crc(unsigned char *buffer, size_t size)
 
 #define CMD_SEQ_INCREMENT 0x10
 
-static int send_cmd(struct fp_dev *dev, unsigned char seq_a,
-	unsigned char seq_b, unsigned char *data, uint16_t len)
+static void fill_send_cmd_urb(struct libusb_bulk_transfer *msg,
+	unsigned char seq_a, unsigned char seq_b, const unsigned char *data,
+	uint16_t len)
 {
-	int r;
-	int transferred;
 	uint16_t crc;
 
 	/* 9 bytes extra for: 4 byte 'Ciao', 1 byte A, 1 byte B | lenHI,
@@ -139,17 +144,15 @@ static int send_cmd(struct fp_dev *dev, unsigned char seq_a,
 	size_t urblen = len + 9;
 	unsigned char *buf;
 
-	struct libusb_bulk_transfer msg = {
-		.endpoint = EP_OUT,
-		.length = urblen,
-	};
-
 	if (!data && len > 0) {
 		fp_err("len>0 but no data?");
-		return -EINVAL;
+		return;
 	}
 	
+	msg->endpoint = EP_OUT;
+	msg->length = urblen;
 	buf = g_malloc(urblen);
+	msg->data = buf;
 
 	/* Write header */
 	strncpy(buf, "Ciao", 4);
@@ -166,30 +169,17 @@ static int send_cmd(struct fp_dev *dev, unsigned char seq_a,
 	crc = GUINT16_TO_BE(udf_crc(buf + 4, urblen - 6));
 	buf[urblen - 2] = crc >> 8;
 	buf[urblen - 1] = crc & 0xff;
-
-	msg.data = buf;
-	r = libusb_bulk_transfer(dev->udev, &msg, &transferred, TIMEOUT);
-	g_free(buf);
-	if (r < 0) {
-		fp_err("cmd write failed, code %d", r);
-		return r;
-	} else if ((unsigned int) transferred < urblen) {
-		fp_err("cmd write too short (%d/%d)", r, urblen);
-		return -EIO;
-	}
-
-	return 0;
 }
 
-static int send_cmd28(struct fp_dev *dev, unsigned char subcmd,
-	unsigned char *data, uint16_t innerlen)
+static void fill_send_cmd28_urb(struct fp_dev *dev,
+	struct libusb_bulk_transfer *msg, unsigned char subcmd,
+	const unsigned char *data, uint16_t innerlen)
 {
 	uint16_t _innerlen = innerlen;
 	size_t len = innerlen + 6;
 	unsigned char *buf = g_malloc0(len);
 	struct upekts_dev *upekdev = dev->priv;
 	uint8_t seq = upekdev->seq + CMD_SEQ_INCREMENT;
-	int r;
 
 	fp_dbg("seq=%02x subcmd=%02x with %d bytes of data", seq, subcmd, innerlen);
 
@@ -200,122 +190,88 @@ static int send_cmd28(struct fp_dev *dev, unsigned char subcmd,
 	buf[5] = subcmd;
 	memcpy(buf + 6, data, innerlen);
 
-	r = send_cmd(dev, 0, seq, buf, len);
-	if (r == 0)
-		upekdev->seq = seq;
+	fill_send_cmd_urb(msg, 0, seq, buf, len);
+	upekdev->seq = seq;
 
 	g_free(buf);
-	return r;
 }
 
-static int send_cmdresponse(struct fp_dev *dev, unsigned char seq,
-	unsigned char *data, uint8_t len)
+static void fill_send_cmdresponse_urb(struct libusb_bulk_transfer *msg,
+	unsigned char seq, const unsigned char *data, uint8_t len)
 {
 	fp_dbg("seq=%02x len=%d", seq, len);
-	return send_cmd(dev, seq, 0, data, len);
-}
-
-static unsigned char *__read_msg(struct fp_dev *dev, size_t *data_len)
-{
-#define MSG_READ_BUF_SIZE 0x40
-#define MAX_DATA_IN_READ_BUF (MSG_READ_BUF_SIZE - 9)
-	unsigned char *buf = g_malloc(MSG_READ_BUF_SIZE);
-	size_t buf_size = MSG_READ_BUF_SIZE;
-	uint16_t computed_crc, msg_crc;
-	uint16_t len;
-	int r;
-	int transferred;
-
-	struct libusb_bulk_transfer msg = {
-		.endpoint = EP_IN,
-		.data = buf,
-		.length = buf_size,
-	};
-
-	r = libusb_bulk_transfer(dev->udev, &msg, &transferred, TIMEOUT);
-	if (r < 0) {
-		fp_err("msg read failed, code %d", r);
-		goto err;
-	} else if (transferred < 9) {
-		fp_err("msg read too short (%d/%d)", r, buf_size);
-		goto err;
-	}
-
-	if (strncmp(buf, "Ciao", 4) != 0) {
-		fp_err("no Ciao for you!!");
-		goto err;
-	}
-
-	len = GUINT16_FROM_LE(((buf[5] & 0xf) << 8) | buf[6]);
-
-	if (r != MSG_READ_BUF_SIZE && (len + 9) < r) {
-		/* Check that the length claimed inside the message is in line with
-		 * the amount of data that was transferred over USB. */
-		fp_err("msg didn't include enough data, expected=%d recv=%d",
-			len + 9, r);
-		goto err;
-	}
-
-	/* We use a 64 byte buffer for reading messages. However, sometimes
-	 * messages are longer, in which case we have to do another USB bulk read
-	 * to read the remainder. This is handled below. */
-	if (len > MAX_DATA_IN_READ_BUF) {
-		int needed = len - MAX_DATA_IN_READ_BUF;
-		struct libusb_bulk_transfer extend_msg = {
-			.endpoint = EP_IN,
-			.length = needed,
-		};
-
-		fp_dbg("didn't fit in buffer, need to extend by %d bytes", needed);
-		buf = g_realloc((gpointer) buf, MSG_READ_BUF_SIZE + needed);
-		extend_msg.data = buf + MSG_READ_BUF_SIZE;
-		r = libusb_bulk_transfer(dev->udev, &extend_msg, &transferred, TIMEOUT);
-		if (r < 0) {
-			fp_err("extended msg read failed, code %d", r);
-			goto err;
-		} else if (transferred < needed) {
-			fp_err("extended msg short read (%d/%d)", r, needed);
-			goto err;
-		}
-		buf_size += needed;
-	}
-
-	computed_crc = udf_crc(buf + 4, len + 3);
-	msg_crc = GUINT16_FROM_LE((buf[len + 8] << 8) | buf[len + 7]);
-	if (computed_crc != msg_crc) {
-		fp_err("CRC failed, got %04x expected %04x", msg_crc, computed_crc);
-		goto err;
-	}
-
-	*data_len = buf_size;
-	return buf;
-err:
-	g_free(buf);
-	return NULL;
+	fill_send_cmd_urb(msg, seq, 0, data, len);
 }
 
 enum read_msg_status {
-	READ_MSG_ERROR = -1,
-	READ_MSG_CMD = 1,
-	READ_MSG_RESPONSE = 2,
+	READ_MSG_ERROR,
+	READ_MSG_CMD,
+	READ_MSG_RESPONSE,
 };
 
-static enum read_msg_status read_msg(struct fp_dev *dev, uint8_t *seq,
-	unsigned char *subcmd, unsigned char **data, size_t *data_len)
-{
-#define MSG_READ_BUF_SIZE 0x40
-#define MAX_DATA_IN_READ_BUF (MSG_READ_BUF_SIZE - 9)
-	unsigned char *buf;
-	size_t buf_size;
-	unsigned char code_a;
-	unsigned char code_b;
-	uint16_t len;
-	enum read_msg_status ret = READ_MSG_ERROR;
+typedef void (*read_msg_cb_fn)(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data);
 
-retry:
-	buf = __read_msg(dev, &buf_size);
-	if (!buf)
-		return READ_MSG_ERROR;
+struct read_msg_data {
+	struct fp_dev *dev;
+	read_msg_cb_fn callback;
+	void *user_data;
+};
+
+static int __read_msg_async(struct read_msg_data *udata);
+
+#define READ_MSG_DATA_CB_ERR(udata) (udata)->callback((udata)->dev, \
+	READ_MSG_ERROR, 0, 0, NULL, 0, (udata)->user_data)
+
+static void busy_ack_sent_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint, int rqlength,
+	unsigned char *data, int actual_length, void *_data)
+{
+	struct read_msg_data *udata = (struct read_msg_data *) _data;
+	if (status != FP_URB_COMPLETED || rqlength != actual_length) {
+		READ_MSG_DATA_CB_ERR(udata);
+		g_free(udata);
+	} else {
+		int r = __read_msg_async(udata);
+		if (r < 0) {
+			READ_MSG_DATA_CB_ERR(udata);
+			g_free(udata);
+		}
+	}
+	libusb_urb_handle_free(urbh);
+}
+
+static int busy_ack_retry_read(struct read_msg_data *udata)
+{
+	struct libusb_bulk_transfer msg;
+	libusb_urb_handle *urbh;
+
+	fill_send_cmdresponse_urb(&msg, 0x09, NULL, 0);
+	urbh = libusb_async_bulk_transfer(udata->dev->udev, &msg, busy_ack_sent_cb,
+		udata, TIMEOUT);
+	if (!urbh) {
+		g_free(msg.data);
+		return -EIO;
+	}
+	return 0;
+}
+
+/* Returns 0 if message was handled, 1 if it was a device-busy message, and
+ * negative on error. */
+static int __handle_incoming_msg(struct read_msg_data *udata,
+	unsigned char *buf)
+{
+	uint16_t len = GUINT16_FROM_LE(((buf[5] & 0xf) << 8) | buf[6]);
+	uint16_t computed_crc = udf_crc(buf + 4, len + 3);
+	uint16_t msg_crc = GUINT16_FROM_LE((buf[len + 8] << 8) | buf[len + 7]);
+	unsigned char *retdata = NULL;
+	unsigned char code_a, code_b;
+
+	if (computed_crc != msg_crc) {
+		fp_err("CRC failed, got %04x expected %04x", msg_crc, computed_crc);
+		return -1;
+	}
 
 	code_a = buf[4];
 	code_b = buf[5] & 0xf0;
@@ -327,23 +283,19 @@ retry:
 		fp_dbg("cmd %x from device to driver", code_a);
 
 		if (code_a == 0x08) {
+			int r;
 			fp_dbg("device busy, send busy-ack");
-			send_cmdresponse(dev, 0x09, NULL, 0);
-			g_free(buf);
-			goto retry;
+			r = busy_ack_retry_read(udata);
+			return (r < 0) ? r : 1;
 		}
 
-		if (seq)
-			*seq = code_a;
-		if (data) {
-			if (len > 0) {
-				unsigned char *tmp = g_malloc(len);
-				memcpy(tmp, buf + 7, len);
-				*data = tmp;
-			}
-			*data_len = len;
+		if (len > 0) {
+			retdata = g_malloc(len);
+			memcpy(retdata, buf + 7, len);
 		}
-		ret = READ_MSG_CMD;
+		udata->callback(udata->dev, READ_MSG_CMD, code_a, 0, retdata, len,
+			udata->user_data);
+		g_free(retdata);
 	} else if (!code_a) {
 		/* device sends response to a previously executed command */
 		unsigned char *innerbuf = buf + 7;
@@ -352,70 +304,167 @@ retry:
 
 		if (len < 6) {
 			fp_err("cmd response too short (%d)", len);
-			goto out;
+			return -1;
 		}
 		if (innerbuf[0] != 0x28) {
 			fp_err("cmd response without 28 byte?");
-			goto out;
+			return -1;
 		}
 		if (innerbuf[3] || innerbuf[4]) {
 			fp_err("non-zero bytes in cmd response");
-			goto out;
+			return -1;
 		}
 
 		innerlen = innerbuf[1] | (innerbuf[2] << 8);
 		innerlen = GUINT16_FROM_LE(innerlen) - 3;
 		_subcmd = innerbuf[5];
 		fp_dbg("device responds to subcmd %x with %d bytes", _subcmd, innerlen);
-		if (seq)
-			*seq = code_b;
-		if (subcmd)
-			*subcmd = _subcmd;
-		if (data) {
-			if (innerlen > 0) {
-				unsigned char *tmp = g_malloc(innerlen);
-				memcpy(tmp, innerbuf + 6, innerlen);
-				*data = tmp;
-			}
-			*data_len = innerlen;
+		if (innerlen > 0) {
+			retdata = g_malloc(innerlen);
+			memcpy(retdata, innerbuf + 6, innerlen);
 		}
-		ret = READ_MSG_RESPONSE;
+		udata->callback(udata->dev, READ_MSG_RESPONSE, code_b, _subcmd,
+			retdata, innerlen, udata->user_data);
+		g_free(retdata);
 	} else {
 		fp_err("don't know how to handle this message");
+		return -1;
 	}
-
-out:
-	g_free(buf);
-	return ret;
+	return 0;
 }
 
-static int read_msg28(struct fp_dev *dev, unsigned char subcmd,
-	unsigned char **data, size_t *data_len)
+static void read_msg_extend_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint, int rqlength,
+	unsigned char *data, int actual_length, void *user_data)
 {
-	struct upekts_dev *upekdev = dev->priv;
-	uint8_t _seq;
-	unsigned char _subcmd;
-	enum read_msg_status msgstat;
+	struct read_msg_data *udata = user_data;
+	unsigned char *buf = data - MSG_READ_BUF_SIZE;
+	int handle_result = 0;
 
-	msgstat = read_msg(dev, &_seq, &_subcmd, data, data_len);
-	if (msgstat != READ_MSG_RESPONSE) {
-		fp_err("expected response, got %d seq=%x", msgstat, _seq);
-		return -EPROTO;
+	if (status != FP_URB_COMPLETED) {
+		fp_err("extended msg read failed, code %d", status);
+		goto err;
 	}
-	if (_subcmd != subcmd) {
-		fp_warn("expected response to subcmd %02x, got response to %02x",
-			subcmd, _subcmd);
-		return -EPROTO;
-	}
-	if (_seq != upekdev->seq) {
-		fp_err("expected response to cmd seq=%02x, got response to %02x",
-			upekdev->seq, _seq);
-		return -EPROTO;
+	if (actual_length < rqlength) {
+		fp_err("extended msg short read (%d/%d)", actual_length, rqlength);
+		goto err;
 	}
 
+	handle_result = __handle_incoming_msg(udata, buf);
+	if (handle_result < 0)
+		goto err;
+	goto out;
+
+err:
+	READ_MSG_DATA_CB_ERR(udata);
+out:
+	if (handle_result != 1)
+		g_free(udata);
+	g_free(buf);
+	libusb_urb_handle_free(urbh);
+}
+
+static void read_msg_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint,
+	int rqlength, unsigned char *data, int actual_length, void *user_data)
+{
+	struct read_msg_data *udata = user_data;
+	uint16_t len;
+	int handle_result = 0;
+
+	if (status != FP_URB_COMPLETED) {
+		fp_err("async msg read failed, code %d", status);
+		goto err;
+	}
+	if (actual_length < 9) {
+		fp_err("async msg read too short (%d)", actual_length);
+		goto err;
+	}
+
+	if (strncmp(data, "Ciao", 4) != 0) {
+		fp_err("no Ciao for you!!");
+		goto err;
+	}
+
+	len = GUINT16_FROM_LE(((data[5] & 0xf) << 8) | data[6]);
+	if (actual_length != MSG_READ_BUF_SIZE && (len + 9) > actual_length) {
+		/* Check that the length claimed inside the message is in line with
+		 * the amount of data that was transferred over USB. */
+		fp_err("msg didn't include enough data, expected=%d recv=%d",
+			len + 9, actual_length);
+		goto err;
+	}
+
+	/* We use a 64 byte buffer for reading messages. However, sometimes
+	 * messages are longer, in which case we have to do another USB bulk read
+	 * to read the remainder. This is handled below. */
+	if (len > MAX_DATA_IN_READ_BUF) {
+		int needed = len - MAX_DATA_IN_READ_BUF;
+		libusb_urb_handle *eurbh;
+		struct libusb_bulk_transfer extend_msg = {
+			.endpoint = EP_IN,
+			.length = needed,
+		};
+
+		fp_dbg("didn't fit in buffer, need to extend by %d bytes", needed);
+		data = g_realloc((gpointer) data, MSG_READ_BUF_SIZE + needed);
+		extend_msg.data = data + MSG_READ_BUF_SIZE;
+		eurbh = libusb_async_bulk_transfer(udata->dev->udev, &extend_msg,
+			read_msg_extend_cb, udata, TIMEOUT);
+		if (!eurbh) {
+			fp_err("extended read submission failed");
+			goto err;
+		}
+		libusb_urb_handle_free(urbh);
+		return;
+	}
+
+	handle_result = __handle_incoming_msg(udata, data);
+	if (handle_result < 0)
+		goto err;
+	goto out;
+
+err:
+	READ_MSG_DATA_CB_ERR(udata);
+out:
+	libusb_urb_handle_free(urbh);
+	if (handle_result != 1)
+		g_free(udata);
+	g_free(data);
+}
+
+static int __read_msg_async(struct read_msg_data *udata)
+{
+	unsigned char *buf = g_malloc(MSG_READ_BUF_SIZE);
+	struct libusb_bulk_transfer msg = {
+		.endpoint = EP_IN,
+		.data = buf,
+		.length = MSG_READ_BUF_SIZE,
+	};
+	libusb_urb_handle *urbh = libusb_async_bulk_transfer(udata->dev->udev, &msg,
+		read_msg_cb, udata, TIMEOUT);
+	if (!urbh) {
+		g_free(buf);
+		return -EIO;
+	}
 	return 0;
 }
 
+static int read_msg_async(struct fp_dev *dev, read_msg_cb_fn callback,
+	void *user_data)
+{
+	struct read_msg_data *udata = g_malloc(sizeof(*udata));
+	int r;
+
+	udata->dev = dev;
+	udata->callback = callback;
+	udata->user_data = user_data;
+	r = __read_msg_async(udata);
+	if (r)
+		g_free(udata);
+	return r;
+}
+
 static const unsigned char init_resp03[] = {
 	0x01, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xff, 0x07
 };
@@ -439,115 +488,342 @@ static const unsigned char init28_0b[] = {
 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00
 };
 
-static int do_init(struct fp_dev *dev)
+/* device initialisation state machine */
+
+enum initsm_states {
+	WRITE_CTRL400 = 0,
+	READ_MSG03,
+	SEND_RESP03,
+	READ_MSG05,
+	SEND28_06,
+	READ28_06,
+	SEND28_07,
+	READ28_07,
+	SEND28_08,
+	READ28_08,
+	SEND28_0C,
+	READ28_0C,
+	SEND28_0B,
+	READ28_0B,
+	INITSM_NUM_STATES,
+};
+
+static void initsm_read_msg_response_cb(struct fpi_ssm *ssm,
+	enum read_msg_status status, uint8_t seq,
+	unsigned char expect_subcmd, unsigned char subcmd)
 {
-	enum read_msg_status msgstat;
-	unsigned char dummy = 0x10;
-	uint8_t seq;
-	int r;
+	struct fp_dev *dev = ssm->dev;
+	struct upekts_dev *upekdev = dev->priv;
 
-	struct libusb_control_transfer msg = {
-		.requesttype = LIBUSB_TYPE_VENDOR | LIBUSB_RECIP_DEVICE,
-		.request = 0x0c,
-		.value = 0x0100,
-		.index = 0x0400,
-		.length = sizeof(dummy),
-		.data = &dummy,
-	};
-
-	r = libusb_control_transfer(dev->udev, &msg, TIMEOUT);
-	if (r < 0) {
-		fp_dbg("control write failed\n");
-		return r;
+	if (status != READ_MSG_RESPONSE) {
+		fp_err("expected response, got %d seq=%x in state %d", status, seq,
+			ssm->cur_state);
+		fpi_ssm_mark_aborted(ssm, -1);
+	} else if (subcmd != expect_subcmd) {
+		fp_warn("expected response to subcmd 0x%02x, got response to %02x in "
+			"state %d", expect_subcmd, subcmd, ssm->cur_state);
+		fpi_ssm_mark_aborted(ssm, -1);
+	} else if (seq != upekdev->seq) {
+		fp_err("expected response to cmd seq=%02x, got response to %02x "
+			"in state %d", upekdev->seq, seq, ssm->cur_state);
+		fpi_ssm_mark_aborted(ssm, -1);
+	} else {
+		fp_dbg("state %d completed", ssm->cur_state);
+		fpi_ssm_next_state(ssm);
 	}
-
-	msgstat = read_msg(dev, &seq, NULL, NULL, NULL);
-	if (msgstat != READ_MSG_CMD) {
-		fp_err("expected command, got %d seq=%x", msgstat, seq);
-		return -EPROTO;
-	}
-	if (seq != 3) {
-		fp_err("expected seq=3, got %x", seq);
-		return -EPROTO;
-	}
-
-	r = send_cmdresponse(dev, ++seq, (unsigned char *) init_resp03,
-		sizeof(init_resp03));
-	if (r < 0)
-		return r;
-	
-	msgstat = read_msg(dev, &seq, NULL, NULL, NULL);
-	if (msgstat != READ_MSG_CMD) {
-		fp_err("expected command, got %d seq=%x", msgstat, seq);
-		return -EPROTO;
-	}
-	if (seq != 5) {
-		fp_err("expected seq=5, got %x", seq);
-		return -EPROTO;
-	}
-
-	dummy = 0x04;
-	r = send_cmd28(dev, 0x06, &dummy, 1);
-	if (r < 0)
-		return r;
-	if (read_msg28(dev, 0x06, NULL, NULL) < 0)
-		return r;
-
-	dummy = 0x04;
-	r = send_cmd28(dev, 0x07, &dummy, 1);
-	if (r < 0)
-		return r;
-	if (read_msg28(dev, 0x07, NULL, NULL) < 0)
-		return r;
-
-	r = send_cmd28(dev, 0x08, (unsigned char *) init28_08,
-		sizeof(init28_08));
-	if (r < 0)
-		return r;
-	if (read_msg28(dev, 0x08, NULL, NULL) < 0)
-		return r;
-
-	r = send_cmd28(dev, 0x0c, (unsigned char *) init28_0c,
-		sizeof(init28_0c));
-	if (r < 0)
-		return r;
-	if (read_msg28(dev, 0x0c, NULL, NULL) < 0)
-		return r;
-
-	r = send_cmd28(dev, 0x0b, (unsigned char *) init28_0b,
-		sizeof(init28_0b));
-	if (r < 0)
-		return r;
-	if (read_msg28(dev, 0x0b, NULL, NULL) < 0)
-		return r;
-
-	return 0;
 }
 
-static int do_deinit(struct fp_dev *dev)
+static void read28_0b_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
 {
-	unsigned char dummy = 0;
-	enum read_msg_status msgstat;
-	uint8_t seq;
-	int r;
+	initsm_read_msg_response_cb((struct fpi_ssm *) user_data, status, seq,
+		0x0b, subcmd);
+}
 
-	/* FIXME: either i've misunderstood the message system or this is illegal
-	 * here, since we arent responding to anything. */
-	r = send_cmdresponse(dev, 0x07, &dummy, 1);
-	if (r < 0)
-		return r;
-	
-	msgstat = read_msg(dev, &seq, NULL, NULL, NULL);
-	if (msgstat != READ_MSG_CMD) {
-		fp_err("expected command, got %d seq=%x", msgstat, seq);
-		return -EPROTO;
+static void read28_0c_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	initsm_read_msg_response_cb((struct fpi_ssm *) user_data, status, seq,
+		0x0c, subcmd);
+}
+
+static void read28_08_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	initsm_read_msg_response_cb((struct fpi_ssm *) user_data, status, seq,
+		0x08, subcmd);
+}
+
+static void read28_07_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	initsm_read_msg_response_cb((struct fpi_ssm *) user_data, status, seq,
+		0x07, subcmd);
+}
+
+static void read28_06_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	initsm_read_msg_response_cb((struct fpi_ssm *) user_data, status, seq,
+		0x06, subcmd);
+}
+
+static void initsm_read_msg_cmd_cb(struct fpi_ssm *ssm,
+	enum read_msg_status status, uint8_t expect_seq, uint8_t seq)
+{
+	struct fp_dev *dev = ssm->dev;
+	struct upekts_dev *upekdev = dev->priv;
+
+	if (status == READ_MSG_ERROR) {
+		fpi_ssm_mark_aborted(ssm, -1);
+		return;
+	} else if (status != READ_MSG_CMD) {
+		fp_err("expected command, got %d seq=%x in state %d", status, seq,
+			ssm->cur_state);
+		fpi_ssm_mark_aborted(ssm, -1);
+		return;
 	}
+	upekdev->seq = seq;
+	if (seq != expect_seq) {
+		fp_err("expected seq=%x, got %x in state %d", expect_seq, seq,
+			ssm->cur_state);
+		fpi_ssm_mark_aborted(ssm, -1);
+		return;
+	}
+
+	fpi_ssm_next_state(ssm);
+}
+
+static void read_msg05_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	initsm_read_msg_cmd_cb((struct fpi_ssm *) user_data, status, 5, seq); 
+}
+
+static void read_msg03_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	initsm_read_msg_cmd_cb((struct fpi_ssm *) user_data, status, 3, seq); 
+}
+
+static void ctrl400_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, struct libusb_ctrl_setup *setup,
+	unsigned char *data, int actual_length, void *user_data)
+{
+	struct fpi_ssm *ssm = user_data;
+	if (status == FP_URB_COMPLETED)
+		fpi_ssm_next_state(ssm);
+	else
+		fpi_ssm_mark_aborted(ssm, -1);
+	libusb_urb_handle_free(urbh);
+}
+
+static void initsm_read_msg_handler(struct fpi_ssm *ssm,
+	read_msg_cb_fn callback)
+{
+	int r = read_msg_async(ssm->dev, callback, ssm);
+	if (r < 0) {
+		fp_err("async read msg failed in state %d", ssm->cur_state);
+		fpi_ssm_mark_aborted(ssm, r);
+	}
+}
+
+static void initsm_send_msg_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint,
+	int rqlength, unsigned char *data, int actual_length, void *user_data)
+{
+	struct fpi_ssm *ssm = user_data;
+	if (status == FP_URB_COMPLETED && rqlength == actual_length) {
+		fp_dbg("state %d completed", ssm->cur_state);
+		fpi_ssm_next_state(ssm);
+	} else {
+		fp_err("failed, state=%d rqlength=%d actual_length=%d", ssm->cur_state,
+			rqlength, actual_length);
+		fpi_ssm_mark_aborted(ssm, -1);
+	}
+	libusb_urb_handle_free(urbh);
+}
+
+static void initsm_send_msg28_handler(struct fpi_ssm *ssm,
+	unsigned char subcmd, const unsigned char *data, uint16_t innerlen)
+{
+	struct fp_dev *dev = ssm->dev;
+	struct libusb_urb_handle *urbh;
+	struct libusb_bulk_transfer trf;
+
+	fill_send_cmd28_urb(dev, &trf, subcmd, data, innerlen);
+	urbh = libusb_async_bulk_transfer(dev->udev, &trf, initsm_send_msg_cb,
+		ssm, TIMEOUT);
+	if (!urbh) {
+		fp_err("urb submission failed in state %d", ssm->cur_state);
+		g_free(trf.data);
+		fpi_ssm_mark_aborted(ssm, -EIO);
+	}
+}
+
+static void initsm_run_state(struct fpi_ssm *ssm)
+{
+	struct fp_dev *dev = ssm->dev;
+	struct upekts_dev *upekdev = dev->priv;
+	struct libusb_urb_handle *urbh;
+
+	switch (ssm->cur_state) {
+	case WRITE_CTRL400: ;
+		unsigned char dummy = 0x10;
+		struct libusb_control_transfer ctrl400_trf = {
+			.requesttype = LIBUSB_TYPE_VENDOR | LIBUSB_RECIP_DEVICE,
+			.request = 0x0c,
+			.value = 0x0100,
+			.index = 0x0400,
+			.length = sizeof(dummy),
+			.data = &dummy,
+		};
+
+		urbh = libusb_async_control_transfer(ssm->dev->udev, &ctrl400_trf,
+			ctrl400_cb, ssm, TIMEOUT);
+		if (!urbh)
+			fpi_ssm_mark_aborted(ssm, -1);
+		break;
+	case READ_MSG03:
+		initsm_read_msg_handler(ssm, read_msg03_cb);
+		break;
+	case SEND_RESP03: ;
+		struct libusb_bulk_transfer resp03_trf;
+
+		fill_send_cmdresponse_urb(&resp03_trf, ++upekdev->seq, init_resp03,
+			sizeof(init_resp03));
+		urbh = libusb_async_bulk_transfer(dev->udev, &resp03_trf,
+			initsm_send_msg_cb, ssm, TIMEOUT);
+		if (!urbh) {
+			g_free(resp03_trf.data);
+			fpi_ssm_mark_aborted(ssm, -EIO);
+		}
+		break;
+	case READ_MSG05:
+		initsm_read_msg_handler(ssm, read_msg05_cb);
+		break;
+	case SEND28_06: ;
+		unsigned char dummy28_06 = 0x04;
+		upekdev->seq = 0xf0;
+		initsm_send_msg28_handler(ssm, 0x06, &dummy28_06, 1);
+		break;
+	case READ28_06:
+		initsm_read_msg_handler(ssm, read28_06_cb);
+		break;
+	case SEND28_07: ;
+		unsigned char dummy28_07 = 0x04;
+		initsm_send_msg28_handler(ssm, 0x07, &dummy28_07, 1);
+		break;
+	case READ28_07:
+		initsm_read_msg_handler(ssm, read28_07_cb);
+		break;
+	case SEND28_08:
+		initsm_send_msg28_handler(ssm, 0x08, init28_08, sizeof(init28_08));
+		break;
+	case READ28_08:
+		initsm_read_msg_handler(ssm, read28_08_cb);
+		break;
+	case SEND28_0C:
+		initsm_send_msg28_handler(ssm, 0x0c, init28_0c, sizeof(init28_0c));
+		break;
+	case READ28_0C:
+		initsm_read_msg_handler(ssm, read28_0c_cb);
+		break;
+	case SEND28_0B:
+		initsm_send_msg28_handler(ssm, 0x0b, init28_0b, sizeof(init28_0b));
+		break;
+	case READ28_0B:
+		initsm_read_msg_handler(ssm, read28_0b_cb);
+		break;
+	}
+}
+
+static struct fpi_ssm *initsm_new(struct fp_dev *dev)
+{
+	return fpi_ssm_new(dev, initsm_run_state, INITSM_NUM_STATES);
+}
+
+enum deinitsm_states {
+	SEND_RESP07 = 0,
+	READ_MSG01,
+	DEINITSM_NUM_STATES,
+};
+
+static void send_resp07_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint,
+	int rqlength, unsigned char *data, int actual_length, void *user_data)
+{
+	struct fpi_ssm *ssm = user_data;
+	if (status != FP_URB_COMPLETED)
+		fpi_ssm_mark_aborted(ssm, -EIO);
+	else if (rqlength != actual_length)
+		fpi_ssm_mark_aborted(ssm, -EPROTO);
+	else
+		fpi_ssm_next_state(ssm);
+	libusb_urb_handle_free(urbh);
+}
+
+static void read_msg01_cb(struct fp_dev *dev, enum read_msg_status status,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	struct fpi_ssm *ssm = user_data;
+	struct upekts_dev *upekdev = dev->priv;
+
+	if (status == READ_MSG_ERROR) {
+		fpi_ssm_mark_aborted(ssm, -1);
+		return;
+	} else if (status != READ_MSG_CMD) {
+		fp_err("expected command, got %d seq=%x", status, seq);
+		fpi_ssm_mark_aborted(ssm, -1);
+		return;
+	}
+	upekdev->seq = seq;
 	if (seq != 1) {
 		fp_err("expected seq=1, got %x", seq);
-		return -EPROTO;
+		fpi_ssm_mark_aborted(ssm, -1);
+		return;
 	}
 
-	return 0;
+	fpi_ssm_next_state(ssm);
+}
+
+static void deinitsm_state_handler(struct fpi_ssm *ssm)
+{
+	struct fp_dev *dev = ssm->dev;
+
+	switch (ssm->cur_state) {
+	case SEND_RESP07: ;
+		struct libusb_bulk_transfer msg;
+		struct libusb_urb_handle *urbh;
+		unsigned char dummy = 0;
+
+		fill_send_cmdresponse_urb(&msg, 0x07, &dummy, 1);
+		urbh = libusb_async_bulk_transfer(dev->udev, &msg, send_resp07_cb, ssm,
+			TIMEOUT);
+		if (!urbh) {
+			g_free(msg.data);
+			fpi_ssm_mark_aborted(ssm, -EIO);
+		}
+		break;
+	case READ_MSG01: ;
+		int r = read_msg_async(dev, read_msg01_cb, ssm);
+		if (r < 0)
+			fpi_ssm_mark_aborted(ssm, r);
+		break;
+	}
+}
+
+static struct fpi_ssm *deinitsm_new(struct fp_dev *dev)
+{
+	return fpi_ssm_new(dev, deinitsm_state_handler, DEINITSM_NUM_STATES);
 }
 
 static int dev_init(struct fp_dev *dev, unsigned long driver_data)
@@ -564,6 +840,7 @@ static int dev_init(struct fp_dev *dev, unsigned long driver_data)
 	dev->priv = upekdev;
 	dev->nr_enroll_stages = 3;
 
+	fpi_drvcb_init_complete(dev, 0);
 	return 0;
 }
 
@@ -571,6 +848,7 @@ static void dev_exit(struct fp_dev *dev)
 {
 	libusb_release_interface(dev->udev, 0);
 	g_free(dev->priv);
+	fpi_drvcb_deinit_complete(dev);
 }
 
 static const unsigned char enroll_init[] = {
@@ -583,134 +861,296 @@ static const unsigned char scan_comp[] = {
 /* used for enrollment and verification */
 static const unsigned char poll_data[] = { 0x30, 0x01 };
 
-static int enroll(struct fp_dev *dev, gboolean initial,
-	int stage, struct fp_print_data **_data, struct fp_img **img)
+enum enroll_start_sm_states {
+	RUN_INITSM = 0,
+	ENROLL_INIT,
+	READ_ENROLL_MSG28,
+	ENROLL_START_NUM_STATES,
+};
+
+/* Called when the device initialization state machine completes */
+static void enroll_start_sm_cb_initsm(struct fpi_ssm *initsm)
 {
-	unsigned char *data;
-	size_t data_len;
-	int r;
-	int result = 0;
-	int passed = 0;
+	struct fpi_ssm *enroll_start_ssm = initsm->priv;
+	int error = initsm->error;
 
-	if (initial) {
-		r = do_init(dev);
-		if (r < 0)
-			return r;
+	fpi_ssm_free(initsm);
+	if (error)
+		fpi_ssm_mark_aborted(enroll_start_ssm, error);
+	else
+		fpi_ssm_next_state(enroll_start_ssm);
+}
 
-		r = send_cmd28(dev, 0x02, (unsigned char *) enroll_init,
-			sizeof(enroll_init));
-		if (r < 0)
-			return r;
+/* called when enroll init URB has completed */
+static void enroll_start_sm_cb_init(libusb_dev_handle *devh,
+	libusb_urb_handle *urbh, enum libusb_urb_cb_status status,
+	unsigned char endpoint, int rqlength, unsigned char *data,
+	int actual_length, void *user_data)
+{
+	struct fpi_ssm *ssm = user_data;
+	if (status != FP_URB_COMPLETED)
+		fpi_ssm_mark_aborted(ssm, -EIO);
+	else if (rqlength != actual_length)
+		fpi_ssm_mark_aborted(ssm, -EPROTO);
+	else
+		fpi_ssm_next_state(ssm);
+	libusb_urb_handle_free(urbh);
+}
+
+static void enroll_start_sm_cb_msg28(struct fp_dev *dev,
+	enum read_msg_status status, uint8_t seq, unsigned char subcmd,
+	unsigned char *data, size_t data_len, void *user_data)
+{
+	struct upekts_dev *upekdev = dev->priv;
+	struct fpi_ssm *ssm = user_data;
+
+	if (status != READ_MSG_RESPONSE) {
+		fp_err("expected response, got %d seq=%x", status, seq);
+		fpi_ssm_mark_aborted(ssm, -1);
+	} else if (subcmd != 0) {
+		fp_warn("expected response to subcmd 0, got response to %02x",
+			subcmd);
+		fpi_ssm_mark_aborted(ssm, -1);
+	} else if (seq != upekdev->seq) {
+		fp_err("expected response to cmd seq=%02x, got response to %02x",
+			upekdev->seq, seq);
+		fpi_ssm_mark_aborted(ssm, -1);
+	} else {
+		fpi_ssm_next_state(ssm);
+	}
+}
+
+static void enroll_start_sm_run_state(struct fpi_ssm *ssm)
+{
+	struct fp_dev *dev = ssm->dev;
+
+	switch (ssm->cur_state) {
+	case RUN_INITSM: ;
+		struct fpi_ssm *initsm = initsm_new(dev);
+		initsm->priv = ssm;
+		fpi_ssm_start(initsm, enroll_start_sm_cb_initsm);
+		break;
+	case ENROLL_INIT: ;
+		struct libusb_bulk_transfer msg;
+		struct libusb_urb_handle *urbh;
+		fill_send_cmd28_urb(dev, &msg, 0x02, enroll_init, sizeof(enroll_init));
+		urbh = libusb_async_bulk_transfer(dev->udev, &msg,
+			enroll_start_sm_cb_init, ssm, TIMEOUT);
+		if (!urbh) {
+			g_free(msg.data);
+			fpi_ssm_mark_aborted(ssm, -EIO);
+		}
+		break;
+	case READ_ENROLL_MSG28: ;
 		/* FIXME: protocol misunderstanding here. device receives response
 		 * to subcmd 0 after submitting subcmd 2? */
 		/* actually this is probably a poll response? does the above cmd
 		 * include a 30 01 poll somewhere? */
-		if (read_msg28(dev, 0x00, NULL, NULL) < 0)
-			return -EPROTO;
+		int r = read_msg_async(dev, enroll_start_sm_cb_msg28, ssm);
+		if (r < 0)
+			fpi_ssm_mark_aborted(ssm, r);
+		break;
+	}
+}
+
+static void enroll_iterate(struct fp_dev *dev);
+
+static void e_handle_resp00(struct fp_dev *dev, unsigned char *data,
+	size_t data_len)
+{
+	struct upekts_dev *upekdev = dev->priv;
+	unsigned char status;
+	int result = 0;
+
+	if (data_len != 14) {
+		fp_err("received 3001 poll response of %d bytes?", data_len);
+		fpi_drvcb_enroll_stage_completed(dev, -EPROTO, NULL, NULL);
+		return;
 	}
 
-	while (!result) {
-		unsigned char status;
+	status = data[5];
+	fp_dbg("poll result = %02x", status);
 
-		r = send_cmd28(dev, 0x00, (unsigned char *) poll_data,
-			sizeof(poll_data));
-		if (r < 0)
-			return r;
-		if (read_msg28(dev, 0x00, &data, &data_len) < 0)
-			return -EPROTO;
-
-		if (data_len != 14) {
-			fp_err("received 3001 poll response of %d bytes?", data_len);
-			g_free(data);
-			return -EPROTO;
+	switch (status) {
+	case 0x0c:
+	case 0x0d:
+	case 0x0e:
+		/* if we previously completed a non-last enrollment stage, we'll
+		 * get this code to indicate successful stage completion */
+		if (upekdev->enroll_passed) {
+			result = FP_ENROLL_PASS;
+			upekdev->enroll_passed = FALSE;
 		}
+		/* otherwise it just means "no news" so we poll again */
+		break;
+	case 0x1c: /* FIXME what does this one mean? */
+	case 0x0b: /* FIXME what does this one mean? */
+	case 0x23: /* FIXME what does this one mean? */
+		result = FP_ENROLL_RETRY;
+		break;
+	case 0x0f: /* scan taking too long, remove finger and try again */
+		result = FP_ENROLL_RETRY_REMOVE_FINGER;
+		break;
+	case 0x1e: /* swipe too short */
+		result = FP_ENROLL_RETRY_TOO_SHORT;
+		break;
+	case 0x24: /* finger not centered */
+		result = FP_ENROLL_RETRY_CENTER_FINGER;
+		break;
+	case 0x20:
+		/* finger scanned successfully */
+		/* need to look at the next poll result to determine if enrollment is
+		 * complete or not */
+		upekdev->enroll_passed = 1;
+		break;
+	case 0x00: /* enrollment complete */
+		/* we can now expect the enrollment data on the next poll, so we
+		 * have nothing to do here */
+		break;
+	default:
+		fp_err("unrecognised scan status code %02x", status);
+		result = -EPROTO;
+		break;
+	}
 
-		status = data[5];
-		fp_dbg("poll result = %02x", status);
-
-		/* These codes indicate that we're waiting for a finger scan, so poll
-		 * again */
-		switch (status) {
-		case 0x0c:
-		case 0x0d:
-		case 0x0e:
-			/* no news, poll again */
-			if (passed)
-				result = FP_ENROLL_PASS;
-			break;
-		case 0x1c: /* FIXME what does this one mean? */
-		case 0x0b: /* FIXME what does this one mean? */
-		case 0x23: /* FIXME what does this one mean? */
-			result = FP_ENROLL_RETRY;
-			break;
-		case 0x0f: /* scan taking too long, remove finger and try again */
-			result = FP_ENROLL_RETRY_REMOVE_FINGER;
-			break;
-		case 0x1e: /* swipe too short */
-			result = FP_ENROLL_RETRY_TOO_SHORT;
-			break;
-		case 0x24: /* finger not centered */
-			result = FP_ENROLL_RETRY_CENTER_FINGER;
-			break;
-		case 0x20:
-			/* finger scanned successfully */
-			/* don't break out immediately, need to look at the next
-			 * value to determine if enrollment is complete or not */
-			passed = 1;
-			break;
-		case 0x00:
-			if (passed)
-				result = FP_ENROLL_COMPLETE;
-			break;
-		default:
-			fp_err("unrecognised scan status code %02x", status);
-			result = -EPROTO;
-			break;
-		}
-		g_free(data);
+	if (result) {
+		fpi_drvcb_enroll_stage_completed(dev, result, NULL, NULL);
+		if (result > 0)
+			enroll_iterate(dev);
+	} else {
+		enroll_iterate(dev);
 	}
 
 	/* FIXME: need to extend protocol research to handle the case when
 	 * enrolment fails, e.g. you scan a different finger on each stage */
+	/* FIXME: should do proper tracking of when we expect cmd0 results and
+	 * cmd2 results and enforce it */
+}
 
-	if (result == FP_ENROLL_COMPLETE) {
-		struct fp_print_data *fdata;
-
-		r = send_cmd28(dev, 0x00, (unsigned char *) poll_data,
-			sizeof(poll_data));
-		if (r < 0)
-			return r;
-		/* FIXME: protocol misunderstanding here. device receives response
-		 * to subcmd 0 after submitting subcmd 2? */
-		if (read_msg28(dev, 0x02, &data, &data_len) < 0)
-			return -EPROTO;
-
-		if (data_len < sizeof(scan_comp)) {
-			fp_err("fingerprint data too short (%d bytes)", data_len);
-			result = -EPROTO;
-			goto comp_out;
-		}
-		if (memcmp(data, scan_comp, sizeof(scan_comp)) != 0) {
-			fp_err("unrecognised data prefix %x %x %x %x %x",
-				data[0], data[1], data[2], data[3], data[4]);
-			result = -EPROTO;
-			goto comp_out;
-		}
-		if (!_data) {
-			fp_err("complete but no data storage!");
-			result = FP_ENROLL_COMPLETE;
-			goto comp_out;
-		}
+static void e_handle_resp02(struct fp_dev *dev, unsigned char *data,
+	size_t data_len)
+{
+	struct fp_print_data *fdata = NULL;
+	int result = -EPROTO;
 
+	if (data_len < sizeof(scan_comp)) {
+		fp_err("fingerprint data too short (%d bytes)", data_len);
+	} else if (memcmp(data, scan_comp, sizeof(scan_comp)) != 0) {
+		fp_err("unrecognised data prefix %x %x %x %x %x",
+			data[0], data[1], data[2], data[3], data[4]);
+	} else {
 		fdata = fpi_print_data_new(dev, data_len - sizeof(scan_comp));
-		memcpy(fdata->data, data + sizeof(scan_comp), data_len - sizeof(scan_comp));
-		*_data = fdata;
-comp_out:
-		do_deinit(dev);
-		g_free(data);
+		memcpy(fdata->data, data + sizeof(scan_comp),
+			data_len - sizeof(scan_comp));
+
+		result = FP_ENROLL_COMPLETE;
 	}
 
-	return result;
+	fpi_drvcb_enroll_stage_completed(dev, result, fdata, NULL);
+}
+
+static void enroll_iterate_msg_cb(struct fp_dev *dev,
+	enum read_msg_status msgstat, uint8_t seq, unsigned char subcmd,
+	unsigned char *data, size_t data_len, void *user_data)
+{
+	if (msgstat != READ_MSG_RESPONSE) {
+		fp_err("expected response, got %d seq=%x", msgstat, seq);
+		fpi_drvcb_enroll_stage_completed(dev, -EPROTO, NULL, NULL);
+		return;
+	}
+	if (subcmd == 0) {
+		e_handle_resp00(dev, data, data_len);
+	} else if (subcmd == 2) {
+		e_handle_resp02(dev, data, data_len);
+	} else {
+		fp_err("unexpected subcmd %d", subcmd);
+		fpi_drvcb_enroll_stage_completed(dev, -EPROTO, NULL, NULL);
+	}
+
+}
+
+static void enroll_iterate_cmd_cb(libusb_dev_handle *devh,
+	libusb_urb_handle *urbh, enum libusb_urb_cb_status status,
+	unsigned char endpoint, int rqlength, unsigned char *data,
+	int actual_length, void *user_data)
+{
+	struct fp_dev *dev = user_data;
+
+	if (status != FP_URB_COMPLETED) {
+		fpi_drvcb_enroll_stage_completed(dev, -EIO, NULL, NULL);
+	} else if (rqlength != actual_length) {
+		fpi_drvcb_enroll_stage_completed(dev, -EPROTO, NULL, NULL);
+	} else {
+		int r = read_msg_async(dev, enroll_iterate_msg_cb, NULL);
+		if (r < 0)
+			fpi_drvcb_enroll_stage_completed(dev, r, NULL, NULL);
+	}
+	libusb_urb_handle_free(urbh);
+}
+
+static void enroll_iterate(struct fp_dev *dev)
+{
+	struct libusb_bulk_transfer msg;
+	struct libusb_urb_handle *urbh;
+
+	fill_send_cmd28_urb(dev, &msg, 0x00, poll_data, sizeof(poll_data));
+	urbh = libusb_async_bulk_transfer(dev->udev, &msg, enroll_iterate_cmd_cb,
+		dev, TIMEOUT);
+	if (!urbh) {
+		g_free(msg.data);
+		fpi_drvcb_enroll_stage_completed(dev, -EIO, NULL, NULL);
+	}
+}
+
+static void enroll_started(struct fpi_ssm *ssm)
+{
+	struct fp_dev *dev = ssm->dev;
+	fpi_drvcb_enroll_started(dev, ssm->error);
+
+	if (!ssm->error)
+		enroll_iterate(dev);
+
+	fpi_ssm_free(ssm);
+}
+
+static int enroll_start(struct fp_dev *dev)
+{
+	struct upekts_dev *upekdev = dev->priv;
+
+	/* do_init state machine first */
+	struct fpi_ssm *ssm = fpi_ssm_new(dev, enroll_start_sm_run_state,
+		ENROLL_START_NUM_STATES);
+
+	upekdev->enroll_passed = FALSE;
+	fpi_ssm_start(ssm, enroll_started);
+	return 0;
+}
+
+static void enroll_stop_deinit_cb(struct fpi_ssm *ssm)
+{
+	/* don't really care about errors */
+	fpi_drvcb_enroll_stopped(ssm->dev);
+	fpi_ssm_free(ssm);
+}
+
+static int enroll_stop(struct fp_dev *dev)
+{
+	struct fpi_ssm *ssm = deinitsm_new(dev);
+	fpi_ssm_start(ssm, enroll_stop_deinit_cb);
+	return 0;
+}
+
+static void verify_stop_deinit_cb(struct fpi_ssm *ssm)
+{
+	/* don't really care about errors */
+	fpi_drvcb_verify_stopped(ssm->dev);
+	fpi_ssm_free(ssm);
+}
+
+static void do_verify_stop(struct fp_dev *dev)
+{
+	struct fpi_ssm *ssm = deinitsm_new(dev);
+	fpi_ssm_start(ssm, verify_stop_deinit_cb);
 }
 
 static const unsigned char verify_hdr[] = {
@@ -719,117 +1159,250 @@ static const unsigned char verify_hdr[] = {
 	0x00
 };
 
-static int verify(struct fp_dev *dev, struct fp_print_data *print,
-	struct fp_img **img)
+enum {
+	VERIFY_RUN_INITSM = 0,
+	VERIFY_INIT,
+	VERIFY_NUM_STATES,
+};
+
+/* Called when the device initialization state machine completes */
+static void verify_start_sm_cb_initsm(struct fpi_ssm *initsm)
 {
-	size_t data_len = sizeof(verify_hdr) + print->length;
-	unsigned char *data;
-	int r;
-	unsigned char status;
-	gboolean need_poll = FALSE;
-	gboolean done = FALSE;
+	struct fpi_ssm *verify_start_ssm = initsm->priv;
+	if (initsm->error)
+		fpi_ssm_mark_aborted(verify_start_ssm, initsm->error);
+	else
+		fpi_ssm_next_state(verify_start_ssm);
+	fpi_ssm_free(initsm);
+}
 
-	r = do_init(dev);
-	if (r < 0)
-		return r;
+static void verify_init_2803_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint,
+	int rqlength, unsigned char *data, int actual_length, void *user_data)
+{
+	struct fpi_ssm *ssm = user_data;
+	if (status != FP_URB_COMPLETED)
+		fpi_ssm_mark_aborted(ssm, -EIO);
+	else if (rqlength != actual_length)
+		fpi_ssm_mark_aborted(ssm, -EPROTO);
+	else
+		fpi_ssm_next_state(ssm);
+	libusb_urb_handle_free(urbh);
+}
 
-	data = g_malloc(data_len);
-	memcpy(data, verify_hdr, sizeof(verify_hdr));
-	memcpy(data + sizeof(verify_hdr), print->data, print->length);
+static void verify_start_sm_run_state(struct fpi_ssm *ssm)
+{
+	struct fp_dev *dev = ssm->dev;
 
-	r = send_cmd28(dev, 0x03, data, data_len);
-	if (r < 0)
-		return r;
-	g_free(data);
+	switch (ssm->cur_state) {
+	case VERIFY_RUN_INITSM: ;
+		struct fpi_ssm *initsm = initsm_new(dev);
+		initsm->priv = ssm;
+		fpi_ssm_start(initsm, verify_start_sm_cb_initsm);
+		break;
+	case VERIFY_INIT: ;
+		struct fp_print_data *print = dev->verify_data;
+		size_t data_len = sizeof(verify_hdr) + print->length;
+		unsigned char *data = g_malloc(data_len);
+		struct libusb_bulk_transfer msg;
+		struct libusb_urb_handle *urbh;
 
-	while (!done) {
-		if (need_poll) {
-			r = send_cmd28(dev, 0x00, (unsigned char *) poll_data,
-				sizeof(poll_data));
-			if (r < 0)
-				return r;
-		} else {
-			need_poll = TRUE;
-		}
-		if (read_msg28(dev, 0x00, &data, &data_len) < 0)
-			return -EPROTO;
-
-		if (data_len != 14) {
-			fp_err("received 3001 poll response of %d bytes?", data_len);
-			r = -EPROTO;
-			goto out;
-		}
-
-		status = data[5];
-		fp_dbg("poll result = %02x", status);
-
-		/* These codes indicate that we're waiting for a finger scan, so poll
-		 * again */
-		switch (status) {
-		case 0x0c: /* no news, poll again */
-			break;
-		case 0x20:
-			fp_dbg("processing scan for verification");
-			break;
-		case 0x00:
-			fp_dbg("good image");
-			done = TRUE;
-			break;
-		case 0x1c: /* FIXME what does this one mean? */
-		case 0x0b: /* FIXME what does this one mean? */
-		case 0x23: /* FIXME what does this one mean? */
-			r = FP_VERIFY_RETRY;
-			goto out;
-		case 0x0f: /* scan taking too long, remove finger and try again */
-			r = FP_VERIFY_RETRY_REMOVE_FINGER;
-			goto out;
-		case 0x1e: /* swipe too short */
-			r = FP_VERIFY_RETRY_TOO_SHORT;
-			goto out;
-		case 0x24: /* finger not centered */
-			r = FP_VERIFY_RETRY_CENTER_FINGER;
-			goto out;
-		default:
-			fp_err("unrecognised verify status code %02x", status);
-			r = -EPROTO;
-			goto out;
-		}
+		memcpy(data, verify_hdr, sizeof(verify_hdr));
+		memcpy(data + sizeof(verify_hdr), print->data, print->length);
+		fill_send_cmd28_urb(dev, &msg, 0x03, data, data_len);
 		g_free(data);
+
+		urbh = libusb_async_bulk_transfer(dev->udev, &msg,
+			verify_init_2803_cb, ssm, TIMEOUT);
+		if (!urbh) {
+			g_free(msg.data);
+			fpi_ssm_mark_aborted(ssm, -EIO);
+		}
+		break;
+	}
+}
+
+static void verify_iterate(struct fp_dev *dev);
+
+static void v_handle_resp00(struct fp_dev *dev, unsigned char *data,
+	size_t data_len)
+{
+	unsigned char status;
+	int r = 0;
+
+	if (data_len != 14) {
+		fp_err("received 3001 poll response of %d bytes?", data_len);
+		r = -EPROTO;
+		goto out;
 	}
 
-	if (status == 0x00) {
-		/* poll again for verify result */
-		r = send_cmd28(dev, 0x00, (unsigned char *) poll_data,
-			sizeof(poll_data));
-		if (r < 0)
-			return r;
-		if (read_msg28(dev, 0x03, &data, &data_len) < 0)
-			return -EPROTO;
-		if (data_len < 2) {
-			fp_err("verify result abnormally short!");
-			r = -EPROTO;
-			goto out;
-		}
-		if (data[0] != 0x12) {
-			fp_err("unexpected verify header byte %02x", data[0]);
-			r = -EPROTO;
-			goto out;
-		}
-		if (data[1] == 0x00) {
-			r = FP_VERIFY_NO_MATCH;
-		} else if (data[1] == 0x01) {
-			r = FP_VERIFY_MATCH;
-		} else {
-			fp_err("unrecognised verify result %02x", data[1]);
-			r = -EPROTO;
-			goto out;
-		}
+	status = data[5];
+	fp_dbg("poll result = %02x", status);
+
+	/* These codes indicate that we're waiting for a finger scan, so poll
+	 * again */
+	switch (status) {
+	case 0x0c: /* no news, poll again */
+		break;
+	case 0x20:
+		fp_dbg("processing scan for verification");
+		break;
+	case 0x00:
+		fp_dbg("good image");
+		break;
+	case 0x1c: /* FIXME what does this one mean? */
+	case 0x0b: /* FIXME what does this one mean? */
+	case 0x23: /* FIXME what does this one mean? */
+		r = FP_VERIFY_RETRY;
+		break;
+	case 0x0f: /* scan taking too long, remove finger and try again */
+		r = FP_VERIFY_RETRY_REMOVE_FINGER;
+		break;
+	case 0x1e: /* swipe too short */
+		r = FP_VERIFY_RETRY_TOO_SHORT;
+		break;
+	case 0x24: /* finger not centered */
+		r = FP_VERIFY_RETRY_CENTER_FINGER;
+		break;
+	default:
+		fp_err("unrecognised verify status code %02x", status);
+		r = -EPROTO;
 	}
 
 out:
-	do_deinit(dev);
-	g_free(data);
-	return r;
+	if (r)
+		fpi_drvcb_report_verify_result(dev, r, NULL);
+	if (r >= 0)
+		verify_iterate(dev);
+}
+
+static void v_handle_resp03(struct fp_dev *dev, unsigned char *data,
+	size_t data_len)
+{
+	int r;
+
+	if (data_len < 2) {
+		fp_err("verify result abnormally short!");
+		r = -EPROTO;
+	} else if (data[0] != 0x12) {
+		fp_err("unexpected verify header byte %02x", data[0]);
+		r = -EPROTO;
+	} else if (data[1] == 0x00) {
+		r = FP_VERIFY_NO_MATCH;
+	} else if (data[1] == 0x01) {
+		r = FP_VERIFY_MATCH;
+	} else {
+		fp_err("unrecognised verify result %02x", data[1]);
+		r = -EPROTO;
+	}
+	fpi_drvcb_report_verify_result(dev, r, NULL);
+}
+
+static void verify_rd2800_cb(struct fp_dev *dev, enum read_msg_status msgstat,
+	uint8_t seq, unsigned char subcmd, unsigned char *data, size_t data_len,
+	void *user_data)
+{
+	struct upekts_dev *upekdev = dev->priv;
+
+	if (msgstat != READ_MSG_RESPONSE) {
+		fp_err("expected response, got %d seq=%x", msgstat, seq);
+		fpi_drvcb_report_verify_result(dev, -EPROTO, NULL);
+		return;
+	} else if (seq != upekdev->seq) {
+		fp_err("expected response to cmd seq=%02x, got response to %02x",
+			upekdev->seq, seq);
+		fpi_drvcb_report_verify_result(dev, -EPROTO, NULL);
+		return;
+	}
+
+	if (subcmd == 0)
+		v_handle_resp00(dev, data, data_len);
+	else if (subcmd == 3)
+		v_handle_resp03(dev, data, data_len);
+	else
+		fpi_drvcb_report_verify_result(dev, -EPROTO, NULL);
+}
+
+static void verify_wr2800_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh,
+	enum libusb_urb_cb_status status, unsigned char endpoint,
+	int rqlength, unsigned char *data, int actual_length, void *user_data)
+{
+	struct fp_dev *dev = user_data;
+
+	if (status != FP_URB_COMPLETED) {
+		fpi_drvcb_report_verify_result(dev, -EIO, NULL);
+	} else if (rqlength != actual_length) {
+		fpi_drvcb_report_verify_result(dev, -EIO, NULL);
+	} else {
+		int r = read_msg_async(dev, verify_rd2800_cb, NULL);
+		if (r < 0)
+			fpi_drvcb_report_verify_result(dev, r, NULL);
+	}
+	libusb_urb_handle_free(urbh);
+}
+
+static void verify_iterate(struct fp_dev *dev)
+{
+	struct upekts_dev *upekdev = dev->priv;
+	struct libusb_bulk_transfer msg;
+
+	if (upekdev->stop_verify) {
+		do_verify_stop(dev);
+		return;
+	}
+
+	/* FIXME: this doesn't flow well, should the first cmd be moved from
+	 * verify init to here? */
+	if (upekdev->first_verify_iteration) {
+		int r = read_msg_async(dev, verify_rd2800_cb, NULL);
+		upekdev->first_verify_iteration = FALSE;
+		if (r < 0)
+			fpi_drvcb_report_verify_result(dev, r, NULL);
+	} else {
+		struct libusb_urb_handle *urbh;
+		fill_send_cmd28_urb(dev, &msg, 0x00, poll_data, sizeof(poll_data));
+		urbh = libusb_async_bulk_transfer(dev->udev, &msg, verify_wr2800_cb,
+			dev, TIMEOUT);
+		if (!urbh) {
+			g_free(msg.data);
+			fpi_drvcb_report_verify_result(dev, -EIO, NULL);
+		}
+	}
+}
+
+static void verify_started(struct fpi_ssm *ssm)
+{
+	struct fp_dev *dev = ssm->dev;
+	struct upekts_dev *upekdev = dev->priv;
+
+	fpi_drvcb_verify_started(dev, ssm->error);
+	if (!ssm->error) {
+		upekdev->first_verify_iteration = TRUE;
+		verify_iterate(dev);
+	}
+
+	fpi_ssm_free(ssm);
+}
+
+static int verify_start(struct fp_dev *dev)
+{
+	struct upekts_dev *upekdev = dev->priv;
+	struct fpi_ssm *ssm = fpi_ssm_new(dev, verify_start_sm_run_state,
+		VERIFY_NUM_STATES);
+	upekdev->stop_verify = FALSE;
+	fpi_ssm_start(ssm, verify_started);
+	return 0;
+}
+
+static int verify_stop(struct fp_dev *dev, gboolean iterating)
+{
+	struct upekts_dev *upekdev = dev->priv;
+
+	if (!iterating)
+		do_verify_stop(dev);
+	else
+		upekdev->stop_verify = TRUE;
+	return 0;
 }
 
 static const struct usb_id id_table[] = {
@@ -843,8 +1416,10 @@ struct fp_driver upekts_driver = {
 	.full_name = "UPEK TouchStrip",
 	.id_table = id_table,
 	.init = dev_init,
-	.exit = dev_exit,
-	.enroll = enroll,
-	.verify = verify,
+	.deinit = dev_exit,
+	.enroll_start = enroll_start,
+	.enroll_stop = enroll_stop,
+	.verify_start = verify_start,
+	.verify_stop = verify_stop,
 };