libfprint/libfprint/drivers/uru4000.c
Timo Teräs 7e1646c382 uru4000: fix race condition on waiting power up irq
It can come before we finish reading the status register on some
cases. Arm the irq handler early, and fix the state machine to
handle early irq properly.

https://bugs.freedesktop.org/show_bug.cgi?id=57834
2012-12-04 11:55:10 +01:00

1390 lines
35 KiB
C

/*
* Digital Persona U.are.U 4000/4000B/4500 driver for libfprint
* Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
* Copyright (C) 2012 Timo Teräs <timo.teras@iki.fi>
*
* 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
*/
#define FP_COMPONENT "uru4000"
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <nss.h>
#include <pk11pub.h>
#include <libusb.h>
#include <fp_internal.h>
#include "driver_ids.h"
#define EP_INTR (1 | LIBUSB_ENDPOINT_IN)
#define EP_DATA (2 | LIBUSB_ENDPOINT_IN)
#define USB_RQ 0x04
#define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN)
#define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT)
#define CTRL_TIMEOUT 5000
#define BULK_TIMEOUT 5000
#define IRQ_LENGTH 64
#define CR_LENGTH 16
#define IMAGE_HEIGHT 290
#define IMAGE_WIDTH 384
enum {
IRQDATA_SCANPWR_ON = 0x56aa,
IRQDATA_FINGER_ON = 0x0101,
IRQDATA_FINGER_OFF = 0x0200,
IRQDATA_DEATH = 0x0800,
};
enum {
REG_HWSTAT = 0x07,
REG_SCRAMBLE_DATA_INDEX = 0x33,
REG_SCRAMBLE_DATA_KEY = 0x34,
REG_MODE = 0x4e,
REG_DEVICE_INFO = 0xf0,
/* firmware starts at 0x100 */
REG_RESPONSE = 0x2000,
REG_CHALLENGE = 0x2010,
};
enum {
MODE_INIT = 0x00,
MODE_AWAIT_FINGER_ON = 0x10,
MODE_AWAIT_FINGER_OFF = 0x12,
MODE_CAPTURE = 0x20,
MODE_CAPTURE_AUX = 0x30,
MODE_OFF = 0x70,
MODE_READY = 0x80,
};
enum {
MS_KBD,
MS_INTELLIMOUSE,
MS_STANDALONE,
MS_STANDALONE_V2,
DP_URU4000,
DP_URU4000B,
};
static const struct uru4k_dev_profile {
const char *name;
gboolean auth_cr;
gboolean encryption;
} uru4k_dev_info[] = {
[MS_KBD] = {
.name = "Microsoft Keyboard with Fingerprint Reader",
.auth_cr = FALSE,
},
[MS_INTELLIMOUSE] = {
.name = "Microsoft Wireless IntelliMouse with Fingerprint Reader",
.auth_cr = FALSE,
},
[MS_STANDALONE] = {
.name = "Microsoft Fingerprint Reader",
.auth_cr = FALSE,
},
[MS_STANDALONE_V2] = {
.name = "Microsoft Fingerprint Reader v2",
.auth_cr = TRUE,
},
[DP_URU4000] = {
.name = "Digital Persona U.are.U 4000",
.auth_cr = FALSE,
},
[DP_URU4000B] = {
.name = "Digital Persona U.are.U 4000B",
.auth_cr = FALSE,
.encryption = TRUE,
},
};
typedef void (*irq_cb_fn)(struct fp_img_dev *dev, int status, uint16_t type,
void *user_data);
typedef void (*irqs_stopped_cb_fn)(struct fp_img_dev *dev);
struct uru4k_dev {
const struct uru4k_dev_profile *profile;
uint8_t interface;
enum fp_imgdev_state activate_state;
unsigned char last_reg_rd[16];
unsigned char last_hwstat;
struct libusb_transfer *irq_transfer;
struct libusb_transfer *img_transfer;
void *img_data;
uint16_t img_lines_done, img_block;
uint32_t img_enc_seed;
irq_cb_fn irq_cb;
void *irq_cb_data;
irqs_stopped_cb_fn irqs_stopped_cb;
int rebootpwr_ctr;
int powerup_ctr;
unsigned char powerup_hwstat;
int scanpwr_irq_timeouts;
struct fpi_timeout *scanpwr_irq_timeout;
int fwfixer_offset;
unsigned char fwfixer_value;
CK_MECHANISM_TYPE cipher;
PK11SlotInfo *slot;
PK11SymKey *symkey;
SECItem *param;
};
/* For 2nd generation MS devices */
static const unsigned char crkey[] = {
0x79, 0xac, 0x91, 0x79, 0x5c, 0xa1, 0x47, 0x8e,
0x98, 0xe0, 0x0f, 0x3c, 0x59, 0x8f, 0x5f, 0x4b,
};
/***** REGISTER I/O *****/
typedef void (*write_regs_cb_fn)(struct fp_img_dev *dev, int status,
void *user_data);
struct write_regs_data {
struct fp_img_dev *dev;
write_regs_cb_fn callback;
void *user_data;
};
static void write_regs_cb(struct libusb_transfer *transfer)
{
struct write_regs_data *wrdata = transfer->user_data;
struct libusb_control_setup *setup =
libusb_control_transfer_get_setup(transfer);
int r = 0;
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
r = -EIO;
else if (transfer->actual_length != setup->wLength)
r = -EPROTO;
g_free(transfer->buffer);
libusb_free_transfer(transfer);
wrdata->callback(wrdata->dev, r, wrdata->user_data);
g_free(wrdata);
}
static int write_regs(struct fp_img_dev *dev, uint16_t first_reg,
uint16_t num_regs, unsigned char *values, write_regs_cb_fn callback,
void *user_data)
{
struct write_regs_data *wrdata;
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
unsigned char *data;
int r;
if (!transfer)
return -ENOMEM;
wrdata = g_malloc(sizeof(*wrdata));
wrdata->dev = dev;
wrdata->callback = callback;
wrdata->user_data = user_data;
data = g_malloc(LIBUSB_CONTROL_SETUP_SIZE + num_regs);
memcpy(data + LIBUSB_CONTROL_SETUP_SIZE, values, num_regs);
libusb_fill_control_setup(data, CTRL_OUT, USB_RQ, first_reg, 0, num_regs);
libusb_fill_control_transfer(transfer, dev->udev, data, write_regs_cb,
wrdata, CTRL_TIMEOUT);
r = libusb_submit_transfer(transfer);
if (r < 0) {
g_free(wrdata);
g_free(data);
libusb_free_transfer(transfer);
}
return r;
}
static int write_reg(struct fp_img_dev *dev, uint16_t reg,
unsigned char value, write_regs_cb_fn callback, void *user_data)
{
return write_regs(dev, reg, 1, &value, callback, user_data);
}
typedef void (*read_regs_cb_fn)(struct fp_img_dev *dev, int status,
uint16_t num_regs, unsigned char *data, void *user_data);
struct read_regs_data {
struct fp_img_dev *dev;
read_regs_cb_fn callback;
void *user_data;
};
static void read_regs_cb(struct libusb_transfer *transfer)
{
struct read_regs_data *rrdata = transfer->user_data;
struct libusb_control_setup *setup =
libusb_control_transfer_get_setup(transfer);
unsigned char *data = NULL;
int r = 0;
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
r = -EIO;
else if (transfer->actual_length != setup->wLength)
r = -EPROTO;
else
data = libusb_control_transfer_get_data(transfer);
rrdata->callback(rrdata->dev, r, transfer->actual_length, data, rrdata->user_data);
g_free(rrdata);
g_free(transfer->buffer);
libusb_free_transfer(transfer);
}
static int read_regs(struct fp_img_dev *dev, uint16_t first_reg,
uint16_t num_regs, read_regs_cb_fn callback, void *user_data)
{
struct read_regs_data *rrdata;
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
unsigned char *data;
int r;
if (!transfer)
return -ENOMEM;
rrdata = g_malloc(sizeof(*rrdata));
rrdata->dev = dev;
rrdata->callback = callback;
rrdata->user_data = user_data;
data = g_malloc(LIBUSB_CONTROL_SETUP_SIZE + num_regs);
libusb_fill_control_setup(data, CTRL_IN, USB_RQ, first_reg, 0, num_regs);
libusb_fill_control_transfer(transfer, dev->udev, data, read_regs_cb,
rrdata, CTRL_TIMEOUT);
r = libusb_submit_transfer(transfer);
if (r < 0) {
g_free(rrdata);
g_free(data);
libusb_free_transfer(transfer);
}
return r;
}
/*
* HWSTAT
*
* This register has caused me a lot of headaches. It pretty much defines
* code flow, and if you don't get it right, the pretty lights don't come on.
* I think the situation is somewhat complicated by the fact that writing it
* doesn't affect the read results in the way you'd expect -- but then again
* it does have some obvious effects. Here's what we know
*
* BIT 7: LOW POWER MODE
* When this bit is set, the device is partially turned off or something. Some
* things, like firmware upload, need to be done in this state. But generally
* we want to clear this bit during late initialization, which can sometimes
* be tricky.
*
* BIT 2: SOMETHING WENT WRONG
* Not sure about this, but see the init function, as when we detect it,
* we reboot the device. Well, we mess with hwstat until this evil bit gets
* cleared.
*
* BIT 1: IRQ PENDING
* Just had a brainwave. This bit is set when the device is trying to deliver
* and interrupt to the host. Maybe?
*/
static void response_cb(struct fp_img_dev *dev, int status, void *user_data)
{
struct fpi_ssm *ssm = user_data;
if (status == 0)
fpi_ssm_next_state(ssm);
else
fpi_ssm_mark_aborted(ssm, status);
}
static void challenge_cb(struct fp_img_dev *dev, int status,
uint16_t num_regs, unsigned char *data, void *user_data)
{
struct fpi_ssm *ssm = user_data;
struct uru4k_dev *urudev = dev->priv;
unsigned char *respdata;
PK11Context *ctx;
int r, outlen;
r = status;
if (status != 0) {
fpi_ssm_mark_aborted(ssm, status);
return;
}
/* submit response */
/* produce response from challenge */
respdata = g_malloc(CR_LENGTH);
ctx = PK11_CreateContextBySymKey(urudev->cipher, CKA_ENCRYPT,
urudev->symkey, urudev->param);
if (PK11_CipherOp(ctx, respdata, &outlen, CR_LENGTH, data, CR_LENGTH) != SECSuccess
|| PK11_Finalize(ctx) != SECSuccess) {
fp_err("Failed to encrypt challenge data");
r = -ECONNABORTED;
g_free(respdata);
}
PK11_DestroyContext(ctx, PR_TRUE);
if (r >= 0) {
r = write_regs(dev, REG_RESPONSE, CR_LENGTH, respdata, response_cb, ssm);
g_free(respdata);
}
if (r < 0)
fpi_ssm_mark_aborted(ssm, r);
}
/*
* 2nd generation MS devices added an AES-based challenge/response
* authentication scheme, where the device challenges the authenticity of the
* driver.
*/
static void sm_do_challenge_response(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
int r;
fp_dbg("");
r = read_regs(dev, REG_CHALLENGE, CR_LENGTH, challenge_cb, ssm);
if (r < 0)
fpi_ssm_mark_aborted(ssm, r);
}
/***** INTERRUPT HANDLING *****/
#define IRQ_HANDLER_IS_RUNNING(urudev) ((urudev)->irq_transfer)
static int start_irq_handler(struct fp_img_dev *dev);
static void irq_handler(struct libusb_transfer *transfer)
{
struct fp_img_dev *dev = transfer->user_data;
struct uru4k_dev *urudev = dev->priv;
unsigned char *data = transfer->buffer;
uint16_t type;
int r = 0;
if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
fp_dbg("cancelled");
if (urudev->irqs_stopped_cb)
urudev->irqs_stopped_cb(dev);
urudev->irqs_stopped_cb = NULL;
goto out;
} else if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
r = -EIO;
goto err;
} else if (transfer->actual_length != transfer->length) {
fp_err("short interrupt read? %d", transfer->actual_length);
r = -EPROTO;
goto err;
}
type = GUINT16_FROM_BE(*((uint16_t *) data));
fp_dbg("recv irq type %04x", type);
g_free(data);
libusb_free_transfer(transfer);
/* The 0800 interrupt seems to indicate imminent failure (0 bytes transfer)
* of the next scan. It still appears on occasion. */
if (type == IRQDATA_DEATH)
fp_warn("oh no! got the interrupt OF DEATH! expect things to go bad");
if (urudev->irq_cb)
urudev->irq_cb(dev, 0, type, urudev->irq_cb_data);
else
fp_dbg("ignoring interrupt");
r = start_irq_handler(dev);
if (r == 0)
return;
transfer = NULL;
data = NULL;
err:
if (urudev->irq_cb)
urudev->irq_cb(dev, r, 0, urudev->irq_cb_data);
out:
g_free(data);
libusb_free_transfer(transfer);
urudev->irq_transfer = NULL;
}
static int start_irq_handler(struct fp_img_dev *dev)
{
struct uru4k_dev *urudev = dev->priv;
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
unsigned char *data;
int r;
if (!transfer)
return -ENOMEM;
data = g_malloc(IRQ_LENGTH);
libusb_fill_bulk_transfer(transfer, dev->udev, EP_INTR, data, IRQ_LENGTH,
irq_handler, dev, 0);
urudev->irq_transfer = transfer;
r = libusb_submit_transfer(transfer);
if (r < 0) {
g_free(data);
libusb_free_transfer(transfer);
urudev->irq_transfer = NULL;
}
return r;
}
static void stop_irq_handler(struct fp_img_dev *dev, irqs_stopped_cb_fn cb)
{
struct uru4k_dev *urudev = dev->priv;
struct libusb_transfer *transfer = urudev->irq_transfer;
if (transfer) {
libusb_cancel_transfer(transfer);
urudev->irqs_stopped_cb = cb;
}
}
/***** STATE CHANGING *****/
static int execute_state_change(struct fp_img_dev *dev);
static void finger_presence_irq_cb(struct fp_img_dev *dev, int status,
uint16_t type, void *user_data)
{
if (status)
fpi_imgdev_session_error(dev, status);
else if (type == IRQDATA_FINGER_ON)
fpi_imgdev_report_finger_status(dev, TRUE);
else if (type == IRQDATA_FINGER_OFF)
fpi_imgdev_report_finger_status(dev, FALSE);
else
fp_warn("ignoring unexpected interrupt %04x", type);
}
static void change_state_write_reg_cb(struct fp_img_dev *dev, int status,
void *user_data)
{
if (status)
fpi_imgdev_session_error(dev, status);
}
static int dev_change_state(struct fp_img_dev *dev, enum fp_imgdev_state state)
{
struct uru4k_dev *urudev = dev->priv;
switch (state) {
case IMGDEV_STATE_INACTIVE:
case IMGDEV_STATE_AWAIT_FINGER_ON:
case IMGDEV_STATE_AWAIT_FINGER_OFF:
case IMGDEV_STATE_CAPTURE:
break;
default:
fp_err("unrecognised state %d", state);
return -EINVAL;
}
urudev->activate_state = state;
if (urudev->img_transfer != NULL)
return 0;
return execute_state_change(dev);
}
/***** GENERIC STATE MACHINE HELPER FUNCTIONS *****/
static void sm_write_reg_cb(struct fp_img_dev *dev, int result, void *user_data)
{
struct fpi_ssm *ssm = user_data;
if (result)
fpi_ssm_mark_aborted(ssm, result);
else
fpi_ssm_next_state(ssm);
}
static void sm_write_regs(struct fpi_ssm *ssm, uint16_t first_reg, uint16_t num_regs,
void *data)
{
struct fp_img_dev *dev = ssm->priv;
int r = write_regs(dev, first_reg, num_regs, data, sm_write_reg_cb, ssm);
if (r < 0)
fpi_ssm_mark_aborted(ssm, r);
}
static void sm_write_reg(struct fpi_ssm *ssm, uint16_t reg,
unsigned char value)
{
sm_write_regs(ssm, reg, 1, &value);
}
static void sm_read_reg_cb(struct fp_img_dev *dev, int result,
uint16_t num_regs, unsigned char *data, void *user_data)
{
struct fpi_ssm *ssm = user_data;
struct uru4k_dev *urudev = dev->priv;
if (result) {
fpi_ssm_mark_aborted(ssm, result);
} else {
memcpy(urudev->last_reg_rd, data, num_regs);
fp_dbg("reg value %x", urudev->last_reg_rd[0]);
fpi_ssm_next_state(ssm);
}
}
static void sm_read_regs(struct fpi_ssm *ssm, uint16_t reg, uint16_t num_regs)
{
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
int r;
if (num_regs > sizeof(urudev->last_reg_rd)) {
fpi_ssm_mark_aborted(ssm, -EIO);
return;
}
fp_dbg("read %d regs at %x", num_regs, reg);
r = read_regs(dev, reg, num_regs, sm_read_reg_cb, ssm);
if (r < 0)
fpi_ssm_mark_aborted(ssm, r);
}
static void sm_read_reg(struct fpi_ssm *ssm, uint16_t reg)
{
sm_read_regs(ssm, reg, 1);
}
static void sm_set_hwstat(struct fpi_ssm *ssm, unsigned char value)
{
fp_dbg("set %02x", value);
sm_write_reg(ssm, REG_HWSTAT, value);
}
/***** IMAGING LOOP *****/
enum imaging_states {
IMAGING_CAPTURE,
IMAGING_SEND_INDEX,
IMAGING_READ_KEY,
IMAGING_DECODE,
IMAGING_REPORT_IMAGE,
IMAGING_NUM_STATES
};
static void image_transfer_cb(struct libusb_transfer *transfer)
{
struct fpi_ssm *ssm = transfer->user_data;
if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
fp_dbg("cancelled");
fpi_ssm_mark_aborted(ssm, -ECANCELED);
} else if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
fp_dbg("error");
fpi_ssm_mark_aborted(ssm, -EIO);
} else {
fpi_ssm_next_state(ssm);
}
}
enum {
BLOCKF_CHANGE_KEY = 0x80,
BLOCKF_NO_KEY_UPDATE = 0x04,
BLOCKF_ENCRYPTED = 0x02,
BLOCKF_NOT_PRESENT = 0x01,
};
struct uru4k_image {
uint8_t unknown_00[4];
uint16_t num_lines;
uint8_t key_number;
uint8_t unknown_07[9];
struct {
uint8_t flags;
uint8_t num_lines;
} block_info[15];
uint8_t unknown_2E[18];
uint8_t data[IMAGE_HEIGHT][IMAGE_WIDTH];
};
static uint32_t update_key(uint32_t key)
{
/* linear feedback shift register
* taps at bit positions 1 3 4 7 11 13 20 23 26 29 32 */
uint32_t bit = key & 0x9248144d;
bit ^= bit << 16;
bit ^= bit << 8;
bit ^= bit << 4;
bit ^= bit << 2;
bit ^= bit << 1;
return (bit & 0x80000000) | (key >> 1);
}
static uint32_t do_decode(uint8_t *data, int num_bytes, uint32_t key)
{
uint8_t xorbyte;
int i;
for (i = 0; i < num_bytes - 1; i++) {
/* calculate xor byte and update key */
xorbyte = ((key >> 4) & 1) << 0;
xorbyte |= ((key >> 8) & 1) << 1;
xorbyte |= ((key >> 11) & 1) << 2;
xorbyte |= ((key >> 14) & 1) << 3;
xorbyte |= ((key >> 18) & 1) << 4;
xorbyte |= ((key >> 21) & 1) << 5;
xorbyte |= ((key >> 24) & 1) << 6;
xorbyte |= ((key >> 29) & 1) << 7;
key = update_key(key);
/* decrypt data */
data[i] = data[i+1] ^ xorbyte;
}
/* the final byte is implictly zero */
data[i] = 0;
return update_key(key);
}
static void imaging_run_state(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
struct uru4k_image *img = urudev->img_data;
struct fp_img *fpimg;
uint32_t key;
uint8_t flags, num_lines;
int i, r, to;
char buf[5];
switch (ssm->cur_state) {
case IMAGING_CAPTURE:
urudev->img_lines_done = 0;
urudev->img_block = 0;
libusb_fill_bulk_transfer(urudev->img_transfer, dev->udev, EP_DATA,
urudev->img_data, sizeof(struct uru4k_image), image_transfer_cb, ssm, 0);
r = libusb_submit_transfer(urudev->img_transfer);
if (r < 0)
fpi_ssm_mark_aborted(ssm, -EIO);
break;
case IMAGING_SEND_INDEX:
fp_dbg("hw header lines %d", img->num_lines);
if (img->num_lines >= IMAGE_HEIGHT ||
urudev->img_transfer->actual_length < img->num_lines * IMAGE_WIDTH + 64) {
fp_err("bad captured image (%d lines) or size mismatch %d < %d",
img->num_lines,
urudev->img_transfer->actual_length,
img->num_lines * IMAGE_WIDTH + 64);
fpi_ssm_jump_to_state(ssm, IMAGING_CAPTURE);
return;
}
if (!urudev->profile->encryption) {
fpi_ssm_jump_to_state(ssm, IMAGING_REPORT_IMAGE);
return;
}
buf[0] = img->key_number;
buf[1] = urudev->img_enc_seed;
buf[2] = urudev->img_enc_seed >> 8;
buf[3] = urudev->img_enc_seed >> 16;
buf[4] = urudev->img_enc_seed >> 24;
sm_write_regs(ssm, REG_SCRAMBLE_DATA_INDEX, 5, buf);
break;
case IMAGING_READ_KEY:
sm_read_regs(ssm, REG_SCRAMBLE_DATA_KEY, 4);
break;
case IMAGING_DECODE:
key = urudev->last_reg_rd[0];
key |= urudev->last_reg_rd[1] << 8;
key |= urudev->last_reg_rd[2] << 16;
key |= urudev->last_reg_rd[3] << 24;
key ^= urudev->img_enc_seed;
fp_dbg("encryption id %02x -> key %08x", img->key_number, key);
while (urudev->img_block < array_n_elements(img->block_info) &&
urudev->img_lines_done < img->num_lines) {
flags = img->block_info[urudev->img_block].flags;
num_lines = img->block_info[urudev->img_block].num_lines;
if (num_lines == 0)
break;
fp_dbg("%d %02x %d", urudev->img_block, flags, num_lines);
if (flags & BLOCKF_CHANGE_KEY) {
fp_dbg("changing encryption keys.\n");
img->block_info[urudev->img_block].flags &= ~BLOCKF_CHANGE_KEY;
img->key_number++;
urudev->img_enc_seed = rand();
fpi_ssm_jump_to_state(ssm, IMAGING_SEND_INDEX);
return;
}
switch (flags & (BLOCKF_NO_KEY_UPDATE | BLOCKF_ENCRYPTED)) {
case BLOCKF_ENCRYPTED:
fp_dbg("decoding %d lines", num_lines);
key = do_decode(&img->data[urudev->img_lines_done][0],
IMAGE_WIDTH*num_lines, key);
break;
case 0:
fp_dbg("skipping %d lines", num_lines);
for (r = 0; r < IMAGE_WIDTH*num_lines; r++)
key = update_key(key);
break;
}
if ((flags & BLOCKF_NOT_PRESENT) == 0)
urudev->img_lines_done += num_lines;
urudev->img_block++;
}
fpi_ssm_next_state(ssm);
break;
case IMAGING_REPORT_IMAGE:
fpimg = fpi_img_new_for_imgdev(dev);
to = r = 0;
for (i = 0; i < array_n_elements(img->block_info) && r < img->num_lines; i++) {
flags = img->block_info[i].flags;
num_lines = img->block_info[i].num_lines;
if (num_lines == 0)
break;
memcpy(&fpimg->data[to], &img->data[r][0],
num_lines * IMAGE_WIDTH);
if (!(flags & BLOCKF_NOT_PRESENT))
r += num_lines;
to += num_lines * IMAGE_WIDTH;
}
fpimg->flags = FP_IMG_COLORS_INVERTED;
if (!urudev->profile->encryption)
fpimg->flags |= FP_IMG_V_FLIPPED | FP_IMG_H_FLIPPED;
fpi_imgdev_image_captured(dev, fpimg);
if (urudev->activate_state == IMGDEV_STATE_CAPTURE)
fpi_ssm_jump_to_state(ssm, IMAGING_CAPTURE);
else
fpi_ssm_mark_completed(ssm);
break;
}
}
static void imaging_complete(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
int r = ssm->error;
fpi_ssm_free(ssm);
g_free(urudev->img_data);
urudev->img_data = NULL;
libusb_free_transfer(urudev->img_transfer);
urudev->img_transfer = NULL;
if (r)
fpi_imgdev_session_error(dev, r);
r = execute_state_change(dev);
if (r)
fpi_imgdev_session_error(dev, r);
}
/***** INITIALIZATION *****/
/* After closing an app and setting hwstat to 0x80, my ms keyboard gets in a
* confused state and returns hwstat 0x85. On next app run, we don't get the
* 56aa interrupt. This is the best way I've found to fix it: mess around
* with hwstat until it starts returning more recognisable values. This
* doesn't happen on my other devices: uru4000, uru4000b, ms fp rdr v2
*
* The windows driver copes with this OK, but then again it uploads firmware
* right after reading the 0x85 hwstat, allowing some time to pass before it
* attempts to tweak hwstat again...
*
* This is implemented with a reboot power state machine. the ssm runs during
* initialization if bits 2 and 7 are set in hwstat. it masks off the 4 high
* hwstat bits then checks that bit 1 is set. if not, it pauses before reading
* hwstat again. machine completes when reading hwstat shows bit 1 is set,
* and fails after 100 tries. */
enum rebootpwr_states {
REBOOTPWR_SET_HWSTAT = 0,
REBOOTPWR_GET_HWSTAT,
REBOOTPWR_CHECK_HWSTAT,
REBOOTPWR_PAUSE,
REBOOTPWR_NUM_STATES,
};
static void rebootpwr_pause_cb(void *data)
{
struct fpi_ssm *ssm = data;
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
if (!--urudev->rebootpwr_ctr) {
fp_err("could not reboot device power");
fpi_ssm_mark_aborted(ssm, -EIO);
} else {
fpi_ssm_jump_to_state(ssm, REBOOTPWR_GET_HWSTAT);
}
}
static void rebootpwr_run_state(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
switch (ssm->cur_state) {
case REBOOTPWR_SET_HWSTAT:
urudev->rebootpwr_ctr = 100;
sm_set_hwstat(ssm, urudev->last_hwstat & 0xf);
break;
case REBOOTPWR_GET_HWSTAT:
sm_read_reg(ssm, REG_HWSTAT);
break;
case REBOOTPWR_CHECK_HWSTAT:
urudev->last_hwstat = urudev->last_reg_rd[0];
if (urudev->last_hwstat & 0x1)
fpi_ssm_mark_completed(ssm);
else
fpi_ssm_next_state(ssm);
break;
case REBOOTPWR_PAUSE:
if (fpi_timeout_add(10, rebootpwr_pause_cb, ssm) == NULL)
fpi_ssm_mark_aborted(ssm, -ETIME);
break;
}
}
/* After messing with the device firmware in it's low-power state, we have to
* power it back up and wait for interrupt notification. It's not quite as easy
* as that: the combination of both modifying firmware *and* doing C-R auth on
* my ms fp v2 device causes us not to get to get the 56aa interrupt and
* for the hwstat write not to take effect. We have to loop a few times,
* authenticating each time, until the device wakes up.
*
* This is implemented as the powerup state machine below. Pseudo-code:
status = get_hwstat();
for (i = 0; i < 100; i++) {
set_hwstat(status & 0xf);
if ((get_hwstat() & 0x80) == 0)
break;
usleep(10000);
if (need_auth_cr)
auth_cr();
}
if (tmp & 0x80)
error("could not power up device");
*/
enum powerup_states {
POWERUP_INIT = 0,
POWERUP_SET_HWSTAT,
POWERUP_GET_HWSTAT,
POWERUP_CHECK_HWSTAT,
POWERUP_PAUSE,
POWERUP_CHALLENGE_RESPONSE,
POWERUP_CHALLENGE_RESPONSE_SUCCESS,
POWERUP_NUM_STATES,
};
static void powerup_pause_cb(void *data)
{
struct fpi_ssm *ssm = data;
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
if (!--urudev->powerup_ctr) {
fp_err("could not power device up");
fpi_ssm_mark_aborted(ssm, -EIO);
} else if (!urudev->profile->auth_cr) {
fpi_ssm_jump_to_state(ssm, POWERUP_SET_HWSTAT);
} else {
fpi_ssm_next_state(ssm);
}
}
static void powerup_run_state(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
switch (ssm->cur_state) {
case POWERUP_INIT:
urudev->powerup_ctr = 100;
urudev->powerup_hwstat = urudev->last_hwstat & 0xf;
fpi_ssm_next_state(ssm);
break;
case POWERUP_SET_HWSTAT:
sm_set_hwstat(ssm, urudev->powerup_hwstat);
break;
case POWERUP_GET_HWSTAT:
sm_read_reg(ssm, REG_HWSTAT);
break;
case POWERUP_CHECK_HWSTAT:
urudev->last_hwstat = urudev->last_reg_rd[0];
if ((urudev->last_reg_rd[0] & 0x80) == 0)
fpi_ssm_mark_completed(ssm);
else
fpi_ssm_next_state(ssm);
break;
case POWERUP_PAUSE:
if (fpi_timeout_add(10, powerup_pause_cb, ssm) == NULL)
fpi_ssm_mark_aborted(ssm, -ETIME);
break;
case POWERUP_CHALLENGE_RESPONSE:
sm_do_challenge_response(ssm);
break;
case POWERUP_CHALLENGE_RESPONSE_SUCCESS:
fpi_ssm_jump_to_state(ssm, POWERUP_SET_HWSTAT);
break;
}
}
/*
* This is the main initialization state machine. As pseudo-code:
status = get_hwstat();
// correct device power state
if ((status & 0x84) == 0x84)
run_reboot_sm();
// power device down
if ((status & 0x80) == 0)
set_hwstat(status | 0x80);
// power device up
run_powerup_sm();
await_irq(IRQDATA_SCANPWR_ON);
*/
enum init_states {
INIT_GET_HWSTAT = 0,
INIT_CHECK_HWSTAT_REBOOT,
INIT_REBOOT_POWER,
INIT_CHECK_HWSTAT_POWERDOWN,
INIT_POWERUP,
INIT_AWAIT_SCAN_POWER,
INIT_DONE,
INIT_GET_VERSION,
INIT_REPORT_VERSION,
INIT_NUM_STATES,
};
static void init_scanpwr_irq_cb(struct fp_img_dev *dev, int status,
uint16_t type, void *user_data)
{
struct fpi_ssm *ssm = user_data;
struct uru4k_dev *urudev = dev->priv;
if (status)
fpi_ssm_mark_aborted(ssm, status);
else if (type != IRQDATA_SCANPWR_ON)
fp_dbg("ignoring interrupt");
else if (ssm->cur_state != INIT_AWAIT_SCAN_POWER) {
fp_dbg("early scanpwr interrupt");
urudev->scanpwr_irq_timeouts = -1;
} else {
fp_dbg("late scanpwr interrupt");
fpi_ssm_next_state(ssm);
}
}
static void init_scanpwr_timeout(void *user_data)
{
struct fpi_ssm *ssm = user_data;
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
fp_warn("powerup timed out");
urudev->irq_cb = NULL;
urudev->scanpwr_irq_timeout = NULL;
if (++urudev->scanpwr_irq_timeouts >= 3) {
fp_err("powerup timed out 3 times, giving up");
fpi_ssm_mark_aborted(ssm, -ETIMEDOUT);
} else {
fpi_ssm_jump_to_state(ssm, INIT_GET_HWSTAT);
}
}
static void init_run_state(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
struct uru4k_dev *urudev = dev->priv;
switch (ssm->cur_state) {
case INIT_GET_HWSTAT:
sm_read_reg(ssm, REG_HWSTAT);
break;
case INIT_CHECK_HWSTAT_REBOOT:
urudev->last_hwstat = urudev->last_reg_rd[0];
if ((urudev->last_hwstat & 0x84) == 0x84)
fpi_ssm_next_state(ssm);
else
fpi_ssm_jump_to_state(ssm, INIT_CHECK_HWSTAT_POWERDOWN);
break;
case INIT_REBOOT_POWER: ;
struct fpi_ssm *rebootsm = fpi_ssm_new(dev->dev, rebootpwr_run_state,
REBOOTPWR_NUM_STATES);
rebootsm->priv = dev;
fpi_ssm_start_subsm(ssm, rebootsm);
break;
case INIT_CHECK_HWSTAT_POWERDOWN:
if ((urudev->last_hwstat & 0x80) == 0)
sm_set_hwstat(ssm, urudev->last_hwstat | 0x80);
else
fpi_ssm_next_state(ssm);
break;
case INIT_POWERUP: ;
if (!IRQ_HANDLER_IS_RUNNING(urudev)) {
fpi_ssm_mark_aborted(ssm, -EIO);
break;
}
urudev->irq_cb_data = ssm;
urudev->irq_cb = init_scanpwr_irq_cb;
struct fpi_ssm *powerupsm = fpi_ssm_new(dev->dev, powerup_run_state,
POWERUP_NUM_STATES);
powerupsm->priv = dev;
fpi_ssm_start_subsm(ssm, powerupsm);
break;
case INIT_AWAIT_SCAN_POWER:
if (urudev->scanpwr_irq_timeouts < 0) {
fpi_ssm_next_state(ssm);
break;
}
/* sometimes the 56aa interrupt that we are waiting for never arrives,
* so we include this timeout loop to retry the whole process 3 times
* if we don't get an irq any time soon. */
urudev->scanpwr_irq_timeout = fpi_timeout_add(300,
init_scanpwr_timeout, ssm);
if (!urudev->scanpwr_irq_timeout) {
fpi_ssm_mark_aborted(ssm, -ETIME);
break;
}
break;
case INIT_DONE:
if (urudev->scanpwr_irq_timeout) {
fpi_timeout_cancel(urudev->scanpwr_irq_timeout);
urudev->scanpwr_irq_timeout = NULL;
}
urudev->irq_cb_data = NULL;
urudev->irq_cb = NULL;
fpi_ssm_next_state(ssm);
break;
case INIT_GET_VERSION:
sm_read_regs(ssm, REG_DEVICE_INFO, 16);
break;
case INIT_REPORT_VERSION:
/* Likely hardware revision, and firmware version.
* Not sure which is which. */
fp_info("Versions %02x%02x and %02x%02x",
urudev->last_reg_rd[10], urudev->last_reg_rd[11],
urudev->last_reg_rd[4], urudev->last_reg_rd[5]);
fpi_ssm_mark_completed(ssm);
break;
}
}
static void activate_initsm_complete(struct fpi_ssm *ssm)
{
struct fp_img_dev *dev = ssm->priv;
int r = ssm->error;
fpi_ssm_free(ssm);
if (r) {
fpi_imgdev_activate_complete(dev, r);
return;
}
r = execute_state_change(dev);
fpi_imgdev_activate_complete(dev, r);
}
/* FIXME: having state parameter here is kinda useless, will we ever
* see a scenario where the parameter is useful so early on in the activation
* process? asynchronity means that it'll only be used in a later function
* call. */
static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
{
struct uru4k_dev *urudev = dev->priv;
struct fpi_ssm *ssm;
int r;
r = start_irq_handler(dev);
if (r < 0)
return r;
urudev->scanpwr_irq_timeouts = 0;
urudev->activate_state = state;
ssm = fpi_ssm_new(dev->dev, init_run_state, INIT_NUM_STATES);
ssm->priv = dev;
fpi_ssm_start(ssm, activate_initsm_complete);
return 0;
}
/***** DEINITIALIZATION *****/
static void deactivate_irqs_stopped(struct fp_img_dev *dev)
{
fpi_imgdev_deactivate_complete(dev);
}
static void deactivate_write_reg_cb(struct fp_img_dev *dev, int status,
void *user_data)
{
stop_irq_handler(dev, deactivate_irqs_stopped);
}
static void dev_deactivate(struct fp_img_dev *dev)
{
dev_change_state(dev, IMGDEV_STATE_INACTIVE);
}
static int execute_state_change(struct fp_img_dev *dev)
{
struct uru4k_dev *urudev = dev->priv;
struct fpi_ssm *ssm;
switch (urudev->activate_state) {
case IMGDEV_STATE_INACTIVE:
fp_dbg("deactivating");
urudev->irq_cb = NULL;
urudev->irq_cb_data = NULL;
return write_reg(dev, REG_MODE, MODE_OFF,
deactivate_write_reg_cb, NULL);
break;
case IMGDEV_STATE_AWAIT_FINGER_ON:
fp_dbg("wait finger on");
if (!IRQ_HANDLER_IS_RUNNING(urudev))
return -EIO;
urudev->irq_cb = finger_presence_irq_cb;
return write_reg(dev, REG_MODE, MODE_AWAIT_FINGER_ON,
change_state_write_reg_cb, NULL);
case IMGDEV_STATE_CAPTURE:
fp_dbg("starting capture");
urudev->irq_cb = NULL;
urudev->img_transfer = libusb_alloc_transfer(0);
urudev->img_data = g_malloc(sizeof(struct uru4k_image));
urudev->img_enc_seed = rand();
ssm = fpi_ssm_new(dev->dev, imaging_run_state, IMAGING_NUM_STATES);
ssm->priv = dev;
fpi_ssm_start(ssm, imaging_complete);
return write_reg(dev, REG_MODE, MODE_CAPTURE,
change_state_write_reg_cb, NULL);
case IMGDEV_STATE_AWAIT_FINGER_OFF:
fp_dbg("await finger off");
if (!IRQ_HANDLER_IS_RUNNING(urudev))
return -EIO;
urudev->irq_cb = finger_presence_irq_cb;
return write_reg(dev, REG_MODE, MODE_AWAIT_FINGER_OFF,
change_state_write_reg_cb, NULL);
}
return 0;
}
/***** LIBRARY STUFF *****/
static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
{
struct libusb_config_descriptor *config;
const struct libusb_interface *iface = NULL;
const struct libusb_interface_descriptor *iface_desc;
const struct libusb_endpoint_descriptor *ep;
struct uru4k_dev *urudev;
SECStatus rv;
SECItem item;
int i;
int r;
/* Find fingerprint interface */
r = libusb_get_config_descriptor(libusb_get_device(dev->udev), 0, &config);
if (r < 0) {
fp_err("Failed to get config descriptor");
return r;
}
for (i = 0; i < config->bNumInterfaces; i++) {
const struct libusb_interface *cur_iface = &config->interface[i];
if (cur_iface->num_altsetting < 1)
continue;
iface_desc = &cur_iface->altsetting[0];
if (iface_desc->bInterfaceClass == 255
&& iface_desc->bInterfaceSubClass == 255
&& iface_desc->bInterfaceProtocol == 255) {
iface = cur_iface;
break;
}
}
if (iface == NULL) {
fp_err("could not find interface");
r = -ENODEV;
goto out;
}
/* Find/check endpoints */
if (iface_desc->bNumEndpoints != 2) {
fp_err("found %d endpoints!?", iface_desc->bNumEndpoints);
r = -ENODEV;
goto out;
}
ep = &iface_desc->endpoint[0];
if (ep->bEndpointAddress != EP_INTR
|| (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
LIBUSB_TRANSFER_TYPE_INTERRUPT) {
fp_err("unrecognised interrupt endpoint");
r = -ENODEV;
goto out;
}
ep = &iface_desc->endpoint[1];
if (ep->bEndpointAddress != EP_DATA
|| (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
LIBUSB_TRANSFER_TYPE_BULK) {
fp_err("unrecognised bulk endpoint");
r = -ENODEV;
goto out;
}
/* Device looks like a supported reader */
r = libusb_claim_interface(dev->udev, iface_desc->bInterfaceNumber);
if (r < 0) {
fp_err("interface claim failed");
goto out;
}
/* Initialise NSS early */
rv = NSS_NoDB_Init(".");
if (rv != SECSuccess) {
fp_err("could not initialise NSS");
goto out;
}
urudev = g_malloc0(sizeof(*urudev));
urudev->profile = &uru4k_dev_info[driver_data];
urudev->interface = iface_desc->bInterfaceNumber;
/* Set up encryption */
urudev->cipher = CKM_AES_ECB;
urudev->slot = PK11_GetBestSlot(urudev->cipher, NULL);
if (urudev->slot == NULL) {
fp_err("could not get encryption slot");
goto out;
}
item.type = siBuffer;
item.data = (unsigned char*) crkey;
item.len = sizeof(crkey);
urudev->symkey = PK11_ImportSymKey(urudev->slot,
urudev->cipher,
PK11_OriginUnwrap,
CKA_ENCRYPT,
&item, NULL);
if (urudev->symkey == NULL) {
fp_err("failed to import key into NSS");
PK11_FreeSlot(urudev->slot);
urudev->slot = NULL;
goto out;
}
urudev->param = PK11_ParamFromIV(urudev->cipher, NULL);
dev->priv = urudev;
fpi_imgdev_open_complete(dev, 0);
out:
libusb_free_config_descriptor(config);
return r;
}
static void dev_deinit(struct fp_img_dev *dev)
{
struct uru4k_dev *urudev = dev->priv;
if (urudev->symkey)
PK11_FreeSymKey (urudev->symkey);
if (urudev->param)
SECITEM_FreeItem(urudev->param, PR_TRUE);
if (urudev->slot)
PK11_FreeSlot(urudev->slot);
libusb_release_interface(dev->udev, urudev->interface);
g_free(urudev);
fpi_imgdev_close_complete(dev);
}
static const struct usb_id id_table[] = {
/* ms kbd with fp rdr */
{ .vendor = 0x045e, .product = 0x00bb, .driver_data = MS_KBD },
/* ms intellimouse with fp rdr */
{ .vendor = 0x045e, .product = 0x00bc, .driver_data = MS_INTELLIMOUSE },
/* ms fp rdr (standalone) */
{ .vendor = 0x045e, .product = 0x00bd, .driver_data = MS_STANDALONE },
/* ms fp rdr (standalone) v2 */
{ .vendor = 0x045e, .product = 0x00ca, .driver_data = MS_STANDALONE_V2 },
/* dp uru4000 (standalone) */
{ .vendor = 0x05ba, .product = 0x0007, .driver_data = DP_URU4000 },
/* dp uru4000 (keyboard) */
{ .vendor = 0x05ba, .product = 0x0008, .driver_data = DP_URU4000 },
/* dp uru4000b (standalone) */
{ .vendor = 0x05ba, .product = 0x000a, .driver_data = DP_URU4000B },
/* terminating entry */
{ 0, 0, 0, },
};
struct fp_img_driver uru4000_driver = {
.driver = {
.id = URU4000_ID,
.name = FP_COMPONENT,
.full_name = "Digital Persona U.are.U 4000/4000B/4500",
.id_table = id_table,
.scan_type = FP_SCAN_TYPE_PRESS,
},
.flags = FP_IMGDRV_SUPPORTS_UNCONDITIONAL_CAPTURE,
.img_height = IMAGE_HEIGHT,
.img_width = IMAGE_WIDTH,
.open = dev_init,
.close = dev_deinit,
.activate = dev_activate,
.deactivate = dev_deactivate,
.change_state = dev_change_state,
};