libfprint/libfprint/drivers/upekts.c
Daniel Drake ab5b1ca8e9 upekts: communication handling and enrollment implementation
This completes an enrollment implementation for the upekts driver.
It's based on code from libthinkfinger, but as there seemed to be many
unknown details there, I spent some time analysing the bus messages in
more detail and have effectively rewritten almost all of the borrowed code.

Note that libthinkfinger is GPL licensed, so that reflects on this code too.
At release time I will ask them if they are OK for me to relicense it.
2007-10-13 17:13:25 +01:00

656 lines
18 KiB
C

/*
* UPEK TouchStrip driver for libfprint
* Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
*
* Based in part on libthinkfinger:
* Copyright (C) 2006-2007 Timo Hoenig <thoenig@suse.de>
* Copyright (C) 2006 Pavel Machek <pavel@suse.cz>
*
* LGPL CRC code copied from GStreamer-0.10.10:
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) 2004,2006 Thomas Vander Stichele <thomas at apestaart dot org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#define FP_COMPONENT "upekts"
#include <errno.h>
#include <string.h>
#include <glib.h>
#include <fp_internal.h>
#define EP_IN (1 | USB_ENDPOINT_IN)
#define EP_OUT (2 | USB_ENDPOINT_OUT)
#define TIMEOUT 5000
struct upekts_dev {
uint8_t seq;
};
static const uint16_t crc_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
static uint16_t udf_crc(unsigned char *buffer, size_t size)
{
uint16_t crc = 0;
while (size--)
crc = (uint16_t) ((crc << 8) ^
crc_table[((crc >> 8) & 0x00ff) ^ *buffer++]);
return crc;
}
/*
* MESSAGE FORMAT
*
* Messages to and from the device have the same format.
*
* Byte-wise:
* 'C' 'i' 'a' 'o' A B L <DATA> C1 C2
*
* Ciao prefixes all messages. L is the length of the data, <DATA> is L bytes
* long. C1 and C2 are the UDF-CRC16 for the whole message minus the Ciao
* prefix.
*
* When the device wants to command the driver to do something, it sends
* a message where B=0 and A!=0. The A value indicates the type of command.
* If the system is expected to respond to the command, it sends a message back
* with B=0 and A incremented.
*
* When the driver sends a command to the device, A=0 and B is used as a
* sequence counter. It starts at 0, increments by 0x10 on each command, and
* wraps around.
* After each command is sent, the device responds with another message
* indicating completion of the command including any data that was requested.
* This message has the same A and B values.
*
* When the driver is sending commands as above, and when the device is
* responding, the <DATA> seems to follow this structure:
*
* 28 L 0 0 0 S <INNERDATA>
*
* Where the length of <INNERDATA> is L-3, and S is some kind of subcommand
* code. In the device's response to a command, the subcommand code will be
* unchanged.
*
* After deducing and documenting the above, I found a few places where the
* above doesn't hold true. Those are marked with FIXME's below.
*/
#define CMD_SEQ_INCREMENT 0x10
static int send_cmd(struct fp_dev *dev, unsigned char seq_a,
unsigned char seq_b, unsigned char *data, uint8_t len)
{
int r;
uint16_t crc;
/* 9 bytes extra for: 4 byte 'Ciao', 1 byte A, 1 byte B, 1 byte length,
* 2 byte CRC */
size_t urblen = len + 9;
unsigned char *buf;
if (!data && len > 0) {
fp_err("len>0 but no data?");
return -EINVAL;
}
buf = g_malloc(urblen);
/* Write header */
strncpy(buf, "Ciao", 4);
buf[4] = seq_a;
buf[5] = seq_b;
buf[6] = len;
/* Copy data */
if (data)
memcpy(buf + 7, data, len);
/* Append CRC */
crc = cpu_to_be16(udf_crc(buf + 4, urblen - 6));
buf[urblen - 2] = crc >> 8;
buf[urblen - 1] = crc & 0xff;
r = usb_bulk_write(dev->udev, EP_OUT, buf, urblen, TIMEOUT);
g_free(buf);
if (r < 0) {
fp_err("cmd write failed, code %d", r);
return r;
} else if ((unsigned int) r < 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, uint8_t 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);
buf[0] = 0x28;
buf[1] = innerlen + 3;
buf[5] = subcmd;
memcpy(buf + 6, data, innerlen);
r = send_cmd(dev, 0, seq, buf, len);
if (r == 0)
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)
{
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;
uint8_t len;
int r;
r = usb_bulk_read(dev->udev, EP_IN, buf, buf_size, TIMEOUT);
if (r < 0) {
fp_err("msg read failed, code %d", r);
goto err;
} else if (r < 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 = 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;
fp_dbg("didn't fit in buffer, need to extend by %d bytes", needed);
buf = g_realloc((gpointer) buf, MSG_READ_BUF_SIZE + needed);
r = usb_bulk_read(dev->udev, EP_IN, buf + MSG_READ_BUF_SIZE, needed,
TIMEOUT);
if (r < 0) {
fp_err("extended msg read failed, code %d", r);
goto err;
} else if (r < 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 = le16_to_cpu((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;
}
enum read_msg_status {
READ_MSG_ERROR = -1,
READ_MSG_CMD = 1,
READ_MSG_RESPONSE = 2,
};
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;
uint8_t len;
enum read_msg_status ret = READ_MSG_ERROR;
retry:
buf = __read_msg(dev, &buf_size);
if (!buf)
return READ_MSG_ERROR;
code_a = buf[4];
code_b = buf[5];
len = buf[6];
fp_dbg("A=%02x B=%02x len=%d", code_a, code_b, len);
if (code_a && !code_b) {
/* device sends command to driver */
fp_dbg("cmd %x from device to driver", code_a);
if (code_a == 0x08) {
fp_dbg("device busy, send busy-ack");
send_cmdresponse(dev, 0x09, NULL, 0);
g_free(buf);
goto retry;
}
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;
}
ret = READ_MSG_CMD;
} else if (!code_a) {
/* device sends response to a previously executed command */
unsigned char *innerbuf = buf + 7;
unsigned char _subcmd;
uint8_t innerlen;
if (len < 6) {
fp_err("cmd response too short (%d)", len);
goto out;
}
if (innerbuf[0] != 0x28) {
fp_err("cmd response without 28 byte?");
goto out;
}
if (innerbuf[2] || innerbuf[3] || innerbuf[4]) {
fp_err("non-zero bytes in cmd response");
goto out;
}
innerlen = innerbuf[1] - 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;
}
ret = READ_MSG_RESPONSE;
} else {
fp_err("don't know how to handle this message");
}
out:
g_free(buf);
return ret;
}
static int read_msg28(struct fp_dev *dev, unsigned char subcmd,
unsigned char **data, size_t *data_len)
{
struct upekts_dev *upekdev = dev->priv;
uint8_t _seq;
unsigned char _subcmd;
enum read_msg_status msgstat;
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 (_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;
}
return 0;
}
static const unsigned char init_resp03[] = {
0x01, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xff, 0x07
};
static const unsigned char init28_08[] = {
0x04, 0x83, 0x00, 0x2c, 0x22, 0x23, 0x97, 0xc9, 0xa7, 0x15, 0xa0, 0x8a,
0xab, 0x3c, 0xd0, 0xbf, 0xdb, 0xf3, 0x92, 0x6f, 0xae, 0x3b, 0x1e, 0x44,
0xc4
};
static const unsigned char init28_0c[] = {
0x04, 0x03, 0x00, 0x00, 0x00
};
static const unsigned char init28_0b[] = {
0x04, 0x03, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x64, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a,
0x00, 0x64, 0x00, 0xf4, 0x01, 0x32, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00
};
static int dev_init(struct fp_dev *dev)
{
struct upekts_dev *upekdev;
unsigned char dummy = 0x10;
enum read_msg_status msgstat;
uint8_t seq;
int r;
r = usb_claim_interface(dev->udev, 0);
if (r < 0)
return r;
r = usb_control_msg(dev->udev, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0x0c, 0x100, 0x400, &dummy, sizeof(dummy), TIMEOUT);
if (r < 0)
return r;
upekdev = g_malloc(sizeof(*upekdev));
upekdev->seq = 0xf0; /* incremented to 0x00 before first cmd */
dev->priv = upekdev;
dev->nr_enroll_stages = 3;
msgstat = read_msg(dev, &seq, NULL, NULL, NULL);
if (msgstat != READ_MSG_CMD) {
fp_err("expected command, got %d seq=%x", msgstat, seq);
goto err;
}
if (seq != 3) {
fp_err("expected seq=3, got %x", seq);
goto err;
}
r = send_cmdresponse(dev, ++seq, (unsigned char *) init_resp03,
sizeof(init_resp03));
if (r < 0)
goto err;
msgstat = read_msg(dev, &seq, NULL, NULL, NULL);
if (msgstat != READ_MSG_CMD) {
fp_err("expected command, got %d seq=%x", msgstat, seq);
goto err;
}
if (seq != 5) {
fp_err("expected seq=5, got %x", seq);
goto err;
}
seq = 0;
dummy = 0x04;
r = send_cmd28(dev, 0x06, &dummy, 1);
if (r < 0)
goto err;
if (read_msg28(dev, 0x06, NULL, NULL) < 0)
goto err;
dummy = 0x04;
r = send_cmd28(dev, 0x07, &dummy, 1);
if (r < 0)
goto err;
if (read_msg28(dev, 0x07, NULL, NULL) < 0)
goto err;
r = send_cmd28(dev, 0x08, (unsigned char *) init28_08,
sizeof(init28_08));
if (r < 0)
goto err;
if (read_msg28(dev, 0x08, NULL, NULL) < 0)
goto err;
r = send_cmd28(dev, 0x0c, (unsigned char *) init28_0c,
sizeof(init28_0c));
if (r < 0)
goto err;
if (read_msg28(dev, 0x0c, NULL, NULL) < 0)
goto err;
r = send_cmd28(dev, 0x0b, (unsigned char *) init28_0b,
sizeof(init28_0b));
if (r < 0)
goto err;
if (read_msg28(dev, 0x0b, NULL, NULL) < 0)
goto err;
return 0;
err:
g_free(upekdev);
return -EPROTO;
}
static void dev_exit(struct fp_dev *dev)
{
unsigned char dummy = 0;
/* FIXME: either i've misunderstood the message system or this is illegal
* here, since we arent responding to anything. */
send_cmdresponse(dev, 0x07, &dummy, 1);
// FIXME should read msg A=01
g_free(dev->priv);
}
static const unsigned char enroll_init[] = {
0x02, 0xc0, 0xd4, 0x01, 0x00, 0x04, 0x00, 0x08
};
static const unsigned char scan_comp[] = {
0x12, 0xff, 0xff, 0xff, 0xff /* scan completion, prefixes print data */
};
static enum fp_enroll_status enroll(struct fp_dev *dev, gboolean initial,
int stage, struct fp_print_data **_data)
{
unsigned char poll_data[] = { 0x30, 0x01 };
unsigned char *data;
size_t data_len;
int r;
int result = 0;
int passed = 0;
if (initial) {
r = send_cmd28(dev, 0x02, (unsigned char *) enroll_init,
sizeof(enroll_init));
if (r < 0)
return FP_ENROLL_FAIL;
/* FIXME: protocol misunderstanding here. device receives response
* to subcmd 0 after submitting subcmd 2? */
if (read_msg28(dev, 0x00, NULL, NULL) < 0)
return FP_ENROLL_FAIL;
}
while (!result) {
unsigned char status;
r = send_cmd28(dev, 0x00, poll_data, sizeof(poll_data));
if (r < 0)
return r;
if (read_msg28(dev, 0x00, &data, &data_len) < 0)
return FP_ENROLL_FAIL;
if (data_len != 14) {
fp_err("received 3001 poll response of %d bytes?", data_len);
g_free(data);
return FP_ENROLL_FAIL;
}
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 0x0f: /* scan taking too long, remove finger and try again */
result = FP_ENROLL_RETRY;
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:
passed = 1;
break;
case 0x00:
if (passed)
result = FP_ENROLL_COMPLETE;
break;
default:
fp_err("unrecognised scan status code %02x", status);
result = FP_ENROLL_FAIL;
break;
}
if (result != FP_ENROLL_COMPLETE)
g_free(data);
}
/* FIXME: need to extend protocol research to handle the case when
* enrolment fails, e.g. you scan a different finger on each stage */
if (result == FP_ENROLL_COMPLETE) {
struct fp_print_data *fdata;
unsigned char *buf;
r = send_cmd28(dev, 0x00, 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 FP_ENROLL_FAIL;
if (data_len < sizeof(scan_comp)) {
fp_err("fingerprint data too short (%d bytes)", data_len);
return FP_ENROLL_FAIL;
}
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]);
return FP_ENROLL_FAIL;
}
if (!_data) {
fp_err("complete but no data storage!");
return FP_ENROLL_COMPLETE;
}
fdata = fpi_print_data_new(dev, data_len - sizeof(scan_comp));
buf = fpi_print_data_get_buffer(fdata);
memcpy(buf, data + sizeof(scan_comp), data_len - sizeof(scan_comp));
*_data = fdata;
g_free(data);
}
return result;
}
static const struct usb_id id_table[] = {
{ .vendor = 0x0483, .product = 0x2016 },
{ 0, 0, 0, }, /* terminating entry */
};
const struct fp_driver upekts_driver = {
.name = FP_COMPONENT,
.full_name = "UPEK TouchStrip",
.id_table = id_table,
.init = dev_init,
.exit = dev_exit,
.enroll = enroll,
};