2007-10-26 23:10:32 +00:00
|
|
|
/*
|
|
|
|
* Digital Persona U.are.U 4000/4000B driver for libfprint
|
2008-02-17 00:08:56 +00:00
|
|
|
* Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
|
2007-10-26 23:10:32 +00:00
|
|
|
*
|
|
|
|
* 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>
|
|
|
|
|
2007-11-16 17:25:41 +00:00
|
|
|
#include <openssl/aes.h>
|
2008-01-30 14:16:35 +00:00
|
|
|
#include <libusb.h>
|
2007-10-26 23:10:32 +00:00
|
|
|
|
|
|
|
#include <fp_internal.h>
|
|
|
|
|
2008-01-30 14:16:35 +00:00
|
|
|
#define EP_INTR (1 | LIBUSB_ENDPOINT_IN)
|
|
|
|
#define EP_DATA (2 | LIBUSB_ENDPOINT_IN)
|
2007-10-26 23:10:32 +00:00
|
|
|
#define USB_RQ 0x04
|
2008-03-29 18:11:17 +00:00
|
|
|
#define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN)
|
|
|
|
#define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT)
|
2007-10-26 23:10:32 +00:00
|
|
|
#define CTRL_TIMEOUT 5000
|
|
|
|
#define BULK_TIMEOUT 5000
|
2008-02-17 00:08:56 +00:00
|
|
|
#define DATABLK_RQLEN 0x1b340
|
|
|
|
#define DATABLK_EXPECT 0x1b1c0
|
2007-10-26 23:10:32 +00:00
|
|
|
#define CAPTURE_HDRLEN 64
|
|
|
|
#define IRQ_LENGTH 64
|
2007-11-16 17:25:41 +00:00
|
|
|
#define CR_LENGTH 16
|
2007-10-26 23:10:32 +00:00
|
|
|
|
|
|
|
enum {
|
|
|
|
IRQDATA_SCANPWR_ON = 0x56aa,
|
|
|
|
IRQDATA_FINGER_ON = 0x0101,
|
|
|
|
IRQDATA_FINGER_OFF = 0x0200,
|
2007-10-31 22:42:09 +00:00
|
|
|
IRQDATA_DEATH = 0x0800,
|
2007-10-26 23:10:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
REG_HWSTAT = 0x07,
|
|
|
|
REG_MODE = 0x4e,
|
2008-03-19 17:46:59 +00:00
|
|
|
/* firmware starts at 0x100 */
|
2007-11-16 17:25:41 +00:00
|
|
|
REG_RESPONSE = 0x2000,
|
|
|
|
REG_CHALLENGE = 0x2010,
|
2007-10-26 23:10:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
MODE_INIT = 0x00,
|
|
|
|
MODE_AWAIT_FINGER_ON = 0x10,
|
|
|
|
MODE_AWAIT_FINGER_OFF = 0x12,
|
|
|
|
MODE_CAPTURE = 0x20,
|
|
|
|
MODE_SHUT_UP = 0x30,
|
|
|
|
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;
|
2007-11-16 17:25:41 +00:00
|
|
|
gboolean auth_cr;
|
2007-10-26 23:10:32 +00:00
|
|
|
} uru4k_dev_info[] = {
|
|
|
|
[MS_KBD] = {
|
|
|
|
.name = "Microsoft Keyboard with Fingerprint Reader",
|
2007-11-16 17:25:41 +00:00
|
|
|
.auth_cr = FALSE,
|
2007-10-26 23:10:32 +00:00
|
|
|
},
|
|
|
|
[MS_INTELLIMOUSE] = {
|
|
|
|
.name = "Microsoft Wireless IntelliMouse with Fingerprint Reader",
|
2007-11-16 17:25:41 +00:00
|
|
|
.auth_cr = FALSE,
|
2007-10-26 23:10:32 +00:00
|
|
|
},
|
|
|
|
[MS_STANDALONE] = {
|
|
|
|
.name = "Microsoft Fingerprint Reader",
|
2007-11-16 17:25:41 +00:00
|
|
|
.auth_cr = FALSE,
|
|
|
|
},
|
|
|
|
[MS_STANDALONE_V2] = {
|
|
|
|
.name = "Microsoft Fingerprint Reader v2",
|
|
|
|
.auth_cr = TRUE,
|
2007-11-04 21:12:48 +00:00
|
|
|
},
|
|
|
|
[DP_URU4000] = {
|
|
|
|
.name = "Digital Persona U.are.U 4000",
|
2007-11-16 17:25:41 +00:00
|
|
|
.auth_cr = FALSE,
|
2007-10-26 23:10:32 +00:00
|
|
|
},
|
|
|
|
[DP_URU4000B] = {
|
|
|
|
.name = "Digital Persona U.are.U 4000B",
|
2007-11-16 17:25:41 +00:00
|
|
|
.auth_cr = FALSE,
|
2007-10-26 23:10:32 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
/* As we don't know the encryption scheme, we have to disable encryption
|
|
|
|
* by powering the device down and modifying the firmware. The location of
|
|
|
|
* the encryption control byte changes based on device revision.
|
|
|
|
*
|
|
|
|
* We use a search approach to find it: we look at the 3 bytes of data starting
|
|
|
|
* from these addresses, looking for a pattern "ff X7 41" (where X is dontcare)
|
|
|
|
* When we find a pattern we know that the encryption byte ius the X7 byte.
|
|
|
|
*/
|
|
|
|
static const uint16_t fwenc_offsets[] = {
|
2008-03-20 13:34:48 +00:00
|
|
|
0x510, 0x62d, 0x792, 0x7f4,
|
2008-03-19 17:46:59 +00:00
|
|
|
};
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
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);
|
|
|
|
|
2007-10-26 23:10:32 +00:00
|
|
|
struct uru4k_dev {
|
2007-11-04 21:12:48 +00:00
|
|
|
const struct uru4k_dev_profile *profile;
|
2007-10-26 23:10:32 +00:00
|
|
|
uint8_t interface;
|
2008-02-17 00:08:56 +00:00
|
|
|
enum fp_imgdev_state activate_state;
|
2008-03-19 17:25:53 +00:00
|
|
|
unsigned char last_reg_rd;
|
|
|
|
unsigned char last_hwstat;
|
2008-02-17 00:08:56 +00:00
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
struct libusb_transfer *irq_transfer;
|
|
|
|
struct libusb_transfer *img_transfer;
|
2008-02-17 00:08:56 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2008-03-03 23:24:00 +00:00
|
|
|
int scanpwr_irq_timeouts;
|
|
|
|
struct fpi_timeout *scanpwr_irq_timeout;
|
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
int fwfixer_offset;
|
|
|
|
unsigned char fwfixer_value;
|
|
|
|
|
2007-11-16 17:25:41 +00:00
|
|
|
AES_KEY aeskey;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* 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,
|
2007-10-26 23:10:32 +00:00
|
|
|
};
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
/***** REGISTER I/O *****/
|
|
|
|
|
|
|
|
typedef void (*write_regs_cb_fn)(struct fp_img_dev *dev, int status,
|
2008-02-17 00:08:56 +00:00
|
|
|
void *user_data);
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
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;
|
2008-04-06 18:56:33 +00:00
|
|
|
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
|
2008-03-19 17:25:53 +00:00
|
|
|
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,
|
|
|
|
unsigned char *data, void *user_data);
|
|
|
|
|
|
|
|
struct read_regs_data {
|
2008-02-17 00:08:56 +00:00
|
|
|
struct fp_img_dev *dev;
|
2008-03-19 17:25:53 +00:00
|
|
|
read_regs_cb_fn callback;
|
2008-02-17 00:08:56 +00:00
|
|
|
void *user_data;
|
|
|
|
};
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void read_regs_cb(struct libusb_transfer *transfer)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct read_regs_data *rrdata = transfer->user_data;
|
|
|
|
struct libusb_control_setup *setup =
|
|
|
|
libusb_control_transfer_get_setup(transfer);
|
|
|
|
unsigned char *data = NULL;
|
2008-02-17 00:08:56 +00:00
|
|
|
int r = 0;
|
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
|
2008-02-17 00:08:56 +00:00
|
|
|
r = -EIO;
|
2008-03-19 17:25:53 +00:00
|
|
|
else if (transfer->actual_length != setup->wLength)
|
2008-02-17 00:08:56 +00:00
|
|
|
r = -EPROTO;
|
2008-03-19 17:25:53 +00:00
|
|
|
else
|
|
|
|
data = libusb_control_transfer_get_data(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
rrdata->callback(rrdata->dev, r, data, rrdata->user_data);
|
|
|
|
g_free(rrdata);
|
2008-03-10 11:35:58 +00:00
|
|
|
g_free(transfer->buffer);
|
|
|
|
libusb_free_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
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)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct read_regs_data *rrdata;
|
2008-04-06 18:56:33 +00:00
|
|
|
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
|
2008-03-10 11:35:58 +00:00
|
|
|
unsigned char *data;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (!transfer)
|
|
|
|
return -ENOMEM;
|
2008-02-17 00:08:56 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
rrdata = g_malloc(sizeof(*rrdata));
|
|
|
|
rrdata->dev = dev;
|
|
|
|
rrdata->callback = callback;
|
|
|
|
rrdata->user_data = user_data;
|
2008-02-17 00:08:56 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
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);
|
2008-03-10 11:35:58 +00:00
|
|
|
|
|
|
|
r = libusb_submit_transfer(transfer);
|
|
|
|
if (r < 0) {
|
2008-03-19 17:25:53 +00:00
|
|
|
g_free(rrdata);
|
2008-03-10 11:35:58 +00:00
|
|
|
g_free(data);
|
|
|
|
libusb_free_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
2008-03-10 11:35:58 +00:00
|
|
|
return r;
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static int read_reg(struct fp_img_dev *dev, uint16_t reg,
|
|
|
|
read_regs_cb_fn callback, void *user_data)
|
|
|
|
{
|
|
|
|
return read_regs(dev, reg, 1, callback, user_data);
|
|
|
|
}
|
|
|
|
|
2007-10-31 22:42:09 +00:00
|
|
|
/*
|
|
|
|
* 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?
|
|
|
|
*/
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void response_cb(struct fp_img_dev *dev, int status, void *user_data)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct fpi_ssm *ssm = user_data;
|
|
|
|
if (status == 0)
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
else
|
|
|
|
fpi_ssm_mark_aborted(ssm, status);
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void challenge_cb(struct fp_img_dev *dev, int status,
|
|
|
|
unsigned char *data, void *user_data)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct fpi_ssm *ssm = user_data;
|
2008-02-17 00:08:56 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
unsigned char *respdata;
|
|
|
|
int r;
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
if (status != 0) {
|
|
|
|
fpi_ssm_mark_aborted(ssm, status);
|
|
|
|
return;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
/* submit response */
|
2008-02-17 00:08:56 +00:00
|
|
|
/* produce response from challenge */
|
2008-03-19 17:25:53 +00:00
|
|
|
/* FIXME would this work in-place? */
|
|
|
|
respdata = g_malloc(CR_LENGTH);
|
|
|
|
AES_encrypt(data, respdata, &urudev->aeskey);
|
|
|
|
|
|
|
|
r = write_regs(dev, REG_RESPONSE, CR_LENGTH, respdata, response_cb, ssm);
|
|
|
|
g_free(respdata);
|
|
|
|
if (r < 0)
|
|
|
|
fpi_ssm_mark_aborted(ssm, r);
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/*
|
|
|
|
* 2nd generation MS devices added an AES-based challenge/response
|
|
|
|
* authentication scheme, where the device challenges the authenticity of the
|
|
|
|
* driver.
|
|
|
|
*/
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_do_challenge_response(struct fpi_ssm *ssm)
|
2007-11-16 17:25:41 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
int r;
|
2008-01-30 14:16:35 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
fp_dbg("");
|
2008-03-19 17:25:53 +00:00
|
|
|
r = read_regs(dev, REG_CHALLENGE, CR_LENGTH, challenge_cb, ssm);
|
|
|
|
if (r < 0)
|
|
|
|
fpi_ssm_mark_aborted(ssm, r);
|
2007-11-16 17:25:41 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/***** INTERRUPT HANDLING *****/
|
2008-01-30 14:16:35 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
#define IRQ_HANDLER_IS_RUNNING(urudev) ((urudev)->irq_transfer)
|
2007-11-16 17:25:41 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static int start_irq_handler(struct fp_img_dev *dev);
|
2007-11-16 17:25:41 +00:00
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
static void irq_handler(struct libusb_transfer *transfer)
|
2007-11-16 17:25:41 +00:00
|
|
|
{
|
2008-03-10 11:35:58 +00:00
|
|
|
struct fp_img_dev *dev = transfer->user_data;
|
2007-11-16 17:25:41 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
unsigned char *data = transfer->buffer;
|
2008-02-17 00:08:56 +00:00
|
|
|
uint16_t type;
|
|
|
|
int r = 0;
|
2007-11-16 17:25:41 +00:00
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
|
2008-02-17 00:08:56 +00:00
|
|
|
fp_dbg("cancelled");
|
|
|
|
if (urudev->irqs_stopped_cb)
|
|
|
|
urudev->irqs_stopped_cb(dev);
|
|
|
|
urudev->irqs_stopped_cb = NULL;
|
|
|
|
goto out;
|
2008-03-10 11:35:58 +00:00
|
|
|
} else if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
|
2008-02-17 00:08:56 +00:00
|
|
|
r = -EIO;
|
|
|
|
goto err;
|
2008-03-10 11:35:58 +00:00
|
|
|
} else if (transfer->actual_length != transfer->length) {
|
|
|
|
fp_err("short interrupt read? %d", transfer->actual_length);
|
2008-02-17 00:08:56 +00:00
|
|
|
r = -EPROTO;
|
|
|
|
goto err;
|
2007-11-16 17:25:41 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
type = GUINT16_FROM_BE(*((uint16_t *) data));
|
|
|
|
fp_dbg("recv irq type %04x", type);
|
2008-03-10 11:35:58 +00:00
|
|
|
g_free(data);
|
|
|
|
libusb_free_transfer(transfer);
|
2007-11-16 17:25:41 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/* 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");
|
2007-11-16 17:25:41 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
r = start_irq_handler(dev);
|
|
|
|
if (r == 0)
|
|
|
|
return;
|
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
transfer = NULL;
|
2008-02-17 00:08:56 +00:00
|
|
|
data = NULL;
|
|
|
|
err:
|
|
|
|
if (urudev->irq_cb)
|
|
|
|
urudev->irq_cb(dev, r, 0, urudev->irq_cb_data);
|
|
|
|
out:
|
|
|
|
g_free(data);
|
2008-03-10 11:35:58 +00:00
|
|
|
libusb_free_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
urudev->irq_transfer = NULL;
|
2007-11-16 17:25:41 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static int start_irq_handler(struct fp_img_dev *dev)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
2008-02-17 00:08:56 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-04-06 18:56:33 +00:00
|
|
|
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
|
2008-03-10 11:35:58 +00:00
|
|
|
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;
|
2007-11-04 19:01:15 +00:00
|
|
|
}
|
2008-03-10 11:35:58 +00:00
|
|
|
return r;
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void stop_irq_handler(struct fp_img_dev *dev, irqs_stopped_cb_fn cb)
|
|
|
|
{
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
struct libusb_transfer *transfer = urudev->irq_transfer;
|
|
|
|
if (transfer) {
|
|
|
|
libusb_cancel_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
urudev->irqs_stopped_cb = cb;
|
|
|
|
}
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/***** IMAGING LOOP *****/
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static int start_imaging_loop(struct fp_img_dev *dev);
|
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
static void image_cb(struct libusb_transfer *transfer)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
2008-03-10 11:35:58 +00:00
|
|
|
struct fp_img_dev *dev = transfer->user_data;
|
2008-02-17 00:08:56 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
|
|
|
int hdr_skip = CAPTURE_HDRLEN;
|
|
|
|
int image_size = DATABLK_EXPECT - CAPTURE_HDRLEN;
|
|
|
|
struct fp_img *img;
|
|
|
|
int r = 0;
|
|
|
|
|
2008-04-12 15:58:49 +00:00
|
|
|
/* remove the global reference early: otherwise we may report results,
|
|
|
|
* leading to immediate deactivation of driver, which will potentially
|
|
|
|
* try to cancel an already-completed transfer */
|
|
|
|
urudev->img_transfer = NULL;
|
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
|
2008-02-17 00:08:56 +00:00
|
|
|
fp_dbg("cancelled");
|
2008-03-10 11:35:58 +00:00
|
|
|
g_free(transfer->buffer);
|
|
|
|
libusb_free_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
return;
|
2008-03-10 11:35:58 +00:00
|
|
|
} else if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
|
2008-02-17 00:08:56 +00:00
|
|
|
r = -EIO;
|
|
|
|
goto out;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
if (transfer->actual_length == image_size) {
|
2008-02-17 00:08:56 +00:00
|
|
|
/* no header! this is rather odd, but it happens sometimes with my MS
|
|
|
|
* keyboard */
|
|
|
|
fp_dbg("got image with no header!");
|
|
|
|
hdr_skip = 0;
|
2008-03-10 11:35:58 +00:00
|
|
|
} else if (transfer->actual_length != DATABLK_EXPECT) {
|
|
|
|
fp_err("unexpected image capture size (%d)", transfer->actual_length);
|
2008-02-17 00:08:56 +00:00
|
|
|
r = -EPROTO;
|
|
|
|
goto out;
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
img = fpi_img_new(image_size);
|
2008-03-10 11:35:58 +00:00
|
|
|
memcpy(img->data, transfer->buffer + hdr_skip, image_size);
|
2008-02-17 00:08:56 +00:00
|
|
|
img->flags = FP_IMG_V_FLIPPED | FP_IMG_H_FLIPPED | FP_IMG_COLORS_INVERTED;
|
|
|
|
fpi_imgdev_image_captured(dev, img);
|
|
|
|
|
|
|
|
out:
|
2008-03-10 11:35:58 +00:00
|
|
|
g_free(transfer->buffer);
|
|
|
|
libusb_free_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
if (r == 0)
|
|
|
|
r = start_imaging_loop(dev);
|
|
|
|
|
|
|
|
if (r)
|
|
|
|
fpi_imgdev_session_error(dev, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int start_imaging_loop(struct fp_img_dev *dev)
|
|
|
|
{
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-04-06 18:56:33 +00:00
|
|
|
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
|
2008-03-10 11:35:58 +00:00
|
|
|
unsigned char *data;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (!transfer)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
data = g_malloc(DATABLK_RQLEN);
|
|
|
|
libusb_fill_bulk_transfer(transfer, dev->udev, EP_DATA, data,
|
|
|
|
DATABLK_RQLEN, image_cb, dev, 0);
|
|
|
|
|
|
|
|
urudev->img_transfer = transfer;
|
|
|
|
r = libusb_submit_transfer(transfer);
|
|
|
|
if (r < 0) {
|
|
|
|
g_free(data);
|
|
|
|
libusb_free_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
2007-10-31 22:42:09 +00:00
|
|
|
|
2008-03-10 11:35:58 +00:00
|
|
|
return r;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void stop_imaging_loop(struct fp_img_dev *dev)
|
|
|
|
{
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
struct libusb_transfer *transfer = urudev->img_transfer;
|
|
|
|
if (transfer)
|
|
|
|
libusb_cancel_transfer(transfer);
|
2008-02-17 00:08:56 +00:00
|
|
|
/* FIXME: should probably wait for cancellation to complete */
|
|
|
|
}
|
|
|
|
|
|
|
|
/***** STATE CHANGING *****/
|
2007-10-31 22:42:09 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void finger_presence_irq_cb(struct fp_img_dev *dev, int status,
|
|
|
|
uint16_t type, void *user_data)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
2008-02-17 00:08:56 +00:00
|
|
|
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);
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void change_state_write_reg_cb(struct fp_img_dev *dev, int status,
|
2008-02-17 00:08:56 +00:00
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
if (status)
|
|
|
|
fpi_imgdev_session_error(dev, status);
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static int dev_change_state(struct fp_img_dev *dev, enum fp_imgdev_state state)
|
|
|
|
{
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2007-10-31 22:42:09 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
stop_imaging_loop(dev);
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
switch (state) {
|
|
|
|
case IMGDEV_STATE_AWAIT_FINGER_ON:
|
|
|
|
if (!IRQ_HANDLER_IS_RUNNING(urudev))
|
|
|
|
return -EIO;
|
|
|
|
urudev->irq_cb = finger_presence_irq_cb;
|
2008-03-19 17:25:53 +00:00
|
|
|
return write_reg(dev, REG_MODE, MODE_AWAIT_FINGER_ON,
|
|
|
|
change_state_write_reg_cb, NULL);
|
2008-02-17 00:08:56 +00:00
|
|
|
|
|
|
|
case IMGDEV_STATE_CAPTURE:
|
|
|
|
urudev->irq_cb = NULL;
|
|
|
|
start_imaging_loop(dev);
|
2008-03-19 17:25:53 +00:00
|
|
|
return write_reg(dev, REG_MODE, MODE_CAPTURE, change_state_write_reg_cb,
|
2008-02-17 00:08:56 +00:00
|
|
|
NULL);
|
|
|
|
|
|
|
|
case IMGDEV_STATE_AWAIT_FINGER_OFF:
|
|
|
|
if (!IRQ_HANDLER_IS_RUNNING(urudev))
|
|
|
|
return -EIO;
|
|
|
|
urudev->irq_cb = finger_presence_irq_cb;
|
2008-03-19 17:25:53 +00:00
|
|
|
return write_reg(dev, REG_MODE, MODE_AWAIT_FINGER_OFF,
|
|
|
|
change_state_write_reg_cb, NULL);
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
default:
|
|
|
|
fp_err("unrecognised state %d", state);
|
|
|
|
return -EINVAL;
|
2007-10-31 22:42:09 +00:00
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/***** GENERIC STATE MACHINE HELPER FUNCTIONS *****/
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_write_reg_cb(struct fp_img_dev *dev, int result, void *user_data)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
|
|
|
struct fpi_ssm *ssm = user_data;
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
if (result)
|
|
|
|
fpi_ssm_mark_aborted(ssm, result);
|
2007-10-31 22:42:09 +00:00
|
|
|
else
|
2008-02-17 00:08:56 +00:00
|
|
|
fpi_ssm_next_state(ssm);
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_write_reg(struct fpi_ssm *ssm, uint16_t reg,
|
2008-02-17 00:08:56 +00:00
|
|
|
unsigned char value)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
2008-02-17 00:08:56 +00:00
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
2008-03-19 17:25:53 +00:00
|
|
|
int r = write_reg(dev, reg, value, sm_write_reg_cb, ssm);
|
2007-10-26 23:10:32 +00:00
|
|
|
if (r < 0)
|
2008-02-17 00:08:56 +00:00
|
|
|
fpi_ssm_mark_aborted(ssm, r);
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_read_reg_cb(struct fp_img_dev *dev, int result,
|
|
|
|
unsigned char *data, void *user_data)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct fpi_ssm *ssm = user_data;
|
2008-02-17 00:08:56 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
if (result) {
|
|
|
|
fpi_ssm_mark_aborted(ssm, result);
|
2008-02-17 00:08:56 +00:00
|
|
|
} else {
|
2008-03-19 17:25:53 +00:00
|
|
|
urudev->last_reg_rd = *data;
|
|
|
|
fp_dbg("reg value %x", urudev->last_reg_rd);
|
2008-02-17 00:08:56 +00:00
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
}
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_read_reg(struct fpi_ssm *ssm, uint16_t reg)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
int r;
|
2008-03-19 17:25:53 +00:00
|
|
|
|
|
|
|
fp_dbg("read reg %x", reg);
|
|
|
|
r = read_reg(dev, reg, sm_read_reg_cb, ssm);
|
|
|
|
if (r < 0)
|
|
|
|
fpi_ssm_mark_aborted(ssm, r);
|
|
|
|
}
|
2008-03-10 11:35:58 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_set_mode(struct fpi_ssm *ssm, unsigned char mode)
|
|
|
|
{
|
|
|
|
fp_dbg("mode %02x", mode);
|
|
|
|
sm_write_reg(ssm, REG_MODE, mode);
|
|
|
|
}
|
2008-03-10 11:35:58 +00:00
|
|
|
|
2008-03-19 17:25:53 +00:00
|
|
|
static void sm_set_hwstat(struct fpi_ssm *ssm, unsigned char value)
|
|
|
|
{
|
|
|
|
fp_dbg("set %02x", value);
|
|
|
|
sm_write_reg(ssm, REG_HWSTAT, value);
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
/***** INITIALIZATION *****/
|
|
|
|
|
|
|
|
enum fwfixer_states {
|
|
|
|
FWFIXER_INIT,
|
|
|
|
FWFIXER_READ_NEXT,
|
|
|
|
FWFIXER_WRITE,
|
|
|
|
FWFIXER_NUM_STATES,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void fwfixer_read_cb(struct fp_img_dev *dev, int status,
|
2008-03-19 17:25:53 +00:00
|
|
|
unsigned char *data, void *user_data)
|
2008-02-17 00:08:56 +00:00
|
|
|
{
|
2008-03-19 17:25:53 +00:00
|
|
|
struct fpi_ssm *ssm = user_data;
|
2008-02-17 00:08:56 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
if (status != 0)
|
2008-03-19 17:25:53 +00:00
|
|
|
fpi_ssm_mark_aborted(ssm, status);
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
fp_dbg("data: %02x %02x %02x", data[0], data[1], data[2]);
|
|
|
|
if (data[0] == 0xff && (data[1] & 0x0f) == 0x07 && data[2] == 0x41) {
|
|
|
|
fp_dbg("using offset %x", fwenc_offsets[urudev->fwfixer_offset]);
|
|
|
|
urudev->fwfixer_value = data[1];
|
|
|
|
fpi_ssm_jump_to_state(ssm, FWFIXER_WRITE);
|
2008-02-17 00:08:56 +00:00
|
|
|
} else {
|
2008-03-19 17:46:59 +00:00
|
|
|
fpi_ssm_jump_to_state(ssm, FWFIXER_READ_NEXT);
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
static void fwfixer_run_state(struct fpi_ssm *ssm)
|
2007-11-04 21:12:48 +00:00
|
|
|
{
|
2008-02-17 00:08:56 +00:00
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
2007-11-04 21:12:48 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-03-10 11:35:58 +00:00
|
|
|
int r;
|
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
switch (ssm->cur_state) {
|
|
|
|
case FWFIXER_INIT:
|
|
|
|
urudev->fwfixer_offset = -1;
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
break;
|
|
|
|
case FWFIXER_READ_NEXT: ;
|
|
|
|
int offset = ++urudev->fwfixer_offset;
|
|
|
|
uint16_t try_addr;
|
|
|
|
|
|
|
|
if (offset == G_N_ELEMENTS(fwenc_offsets)) {
|
|
|
|
fp_err("could not find encryption byte");
|
|
|
|
fpi_ssm_mark_aborted(ssm, -ENODEV);
|
|
|
|
return;
|
|
|
|
}
|
2007-11-04 21:12:48 +00:00
|
|
|
|
2008-03-19 17:46:59 +00:00
|
|
|
try_addr = fwenc_offsets[offset];
|
|
|
|
fp_dbg("looking for encryption byte at %x", try_addr);
|
|
|
|
|
|
|
|
r = read_regs(dev, try_addr, 3, fwfixer_read_cb, ssm);
|
|
|
|
if (r < 0)
|
|
|
|
fpi_ssm_mark_aborted(ssm, r);
|
|
|
|
break;
|
|
|
|
case FWFIXER_WRITE: ;
|
|
|
|
uint16_t enc_addr = fwenc_offsets[urudev->fwfixer_offset] + 1;
|
|
|
|
unsigned char cur = urudev->fwfixer_value;
|
|
|
|
unsigned char new = cur & 0xef;
|
|
|
|
if (new == cur) {
|
|
|
|
fp_dbg("encryption is already disabled");
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
} else {
|
|
|
|
fp_dbg("fixing encryption byte at %x to %02x", enc_addr, new);
|
|
|
|
sm_write_reg(ssm, enc_addr, new);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2007-11-04 21:12:48 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/* 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,
|
|
|
|
};
|
2008-01-30 14:16:35 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
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;
|
2007-11-04 21:12:48 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
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);
|
|
|
|
}
|
2007-11-04 21:12:48 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void rebootpwr_run_state(struct fpi_ssm *ssm)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
2008-02-17 00:08:56 +00:00
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
2007-11-16 17:25:41 +00:00
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
switch (ssm->cur_state) {
|
|
|
|
case REBOOTPWR_SET_HWSTAT:
|
|
|
|
urudev->rebootpwr_ctr = 100;
|
2008-03-19 17:25:53 +00:00
|
|
|
sm_set_hwstat(ssm, urudev->last_hwstat & 0xf);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case REBOOTPWR_GET_HWSTAT:
|
2008-03-19 17:25:53 +00:00
|
|
|
sm_read_reg(ssm, REG_HWSTAT);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case REBOOTPWR_CHECK_HWSTAT:
|
2008-03-19 17:25:53 +00:00
|
|
|
urudev->last_hwstat = urudev->last_reg_rd;
|
|
|
|
if (urudev->last_hwstat & 0x1)
|
2008-02-17 00:08:56 +00:00
|
|
|
fpi_ssm_mark_completed(ssm);
|
|
|
|
else
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
break;
|
|
|
|
case REBOOTPWR_PAUSE:
|
2008-03-03 23:23:50 +00:00
|
|
|
if (fpi_timeout_add(10, rebootpwr_pause_cb, ssm) == NULL)
|
|
|
|
fpi_ssm_mark_aborted(ssm, -ETIME);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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();
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
if (tmp & 0x80)
|
|
|
|
error("could not power up device");
|
2007-11-04 21:12:48 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
*/
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
enum powerup_states {
|
|
|
|
POWERUP_INIT = 0,
|
|
|
|
POWERUP_SET_HWSTAT,
|
|
|
|
POWERUP_GET_HWSTAT,
|
|
|
|
POWERUP_CHECK_HWSTAT,
|
|
|
|
POWERUP_PAUSE,
|
|
|
|
POWERUP_CHALLENGE_RESPONSE,
|
2008-03-19 17:25:53 +00:00
|
|
|
POWERUP_CHALLENGE_RESPONSE_SUCCESS,
|
2008-02-17 00:08:56 +00:00
|
|
|
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;
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
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;
|
2008-03-19 17:25:53 +00:00
|
|
|
urudev->powerup_hwstat = urudev->last_hwstat & 0xf;
|
2008-02-17 00:08:56 +00:00
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
break;
|
|
|
|
case POWERUP_SET_HWSTAT:
|
|
|
|
sm_set_hwstat(ssm, urudev->powerup_hwstat);
|
|
|
|
break;
|
|
|
|
case POWERUP_GET_HWSTAT:
|
2008-03-19 17:25:53 +00:00
|
|
|
sm_read_reg(ssm, REG_HWSTAT);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case POWERUP_CHECK_HWSTAT:
|
2008-03-19 17:25:53 +00:00
|
|
|
urudev->last_hwstat = urudev->last_reg_rd;
|
|
|
|
if ((urudev->last_reg_rd & 0x80) == 0)
|
2008-02-17 00:08:56 +00:00
|
|
|
fpi_ssm_mark_completed(ssm);
|
|
|
|
else
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
break;
|
|
|
|
case POWERUP_PAUSE:
|
2008-03-03 23:23:50 +00:00
|
|
|
if (fpi_timeout_add(10, powerup_pause_cb, ssm) == NULL)
|
|
|
|
fpi_ssm_mark_aborted(ssm, -ETIME);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case POWERUP_CHALLENGE_RESPONSE:
|
2008-03-19 17:25:53 +00:00
|
|
|
sm_do_challenge_response(ssm);
|
|
|
|
break;
|
|
|
|
case POWERUP_CHALLENGE_RESPONSE_SUCCESS:
|
|
|
|
fpi_ssm_jump_to_state(ssm, POWERUP_SET_HWSTAT);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
/*
|
|
|
|
* This is the main initialization state machine. As pseudo-code:
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
// disable encryption
|
|
|
|
fwenc = read_firmware_encryption_byte();
|
|
|
|
new = fwenc & 0xef;
|
|
|
|
if (new != fwenc)
|
|
|
|
write_firmware_encryption_byte(new);
|
|
|
|
|
|
|
|
// 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_FIX_FIRMWARE,
|
|
|
|
INIT_POWERUP,
|
|
|
|
INIT_AWAIT_SCAN_POWER,
|
|
|
|
INIT_DONE,
|
|
|
|
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;
|
|
|
|
|
|
|
|
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_err("ignoring scanpwr interrupt due to being in wrong state %d",
|
|
|
|
ssm->cur_state);
|
|
|
|
else
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
}
|
|
|
|
|
2008-03-03 23:24:00 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void init_run_state(struct fpi_ssm *ssm)
|
|
|
|
{
|
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
switch (ssm->cur_state) {
|
|
|
|
case INIT_GET_HWSTAT:
|
2008-03-19 17:25:53 +00:00
|
|
|
sm_read_reg(ssm, REG_HWSTAT);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case INIT_CHECK_HWSTAT_REBOOT:
|
2008-03-19 17:25:53 +00:00
|
|
|
urudev->last_hwstat = urudev->last_reg_rd;
|
|
|
|
if ((urudev->last_hwstat & 0x84) == 0x84)
|
2008-02-17 00:08:56 +00:00
|
|
|
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:
|
2008-03-19 17:25:53 +00:00
|
|
|
if ((urudev->last_hwstat & 0x80) == 0)
|
|
|
|
sm_set_hwstat(ssm, urudev->last_hwstat | 0x80);
|
2008-02-17 00:08:56 +00:00
|
|
|
else
|
|
|
|
fpi_ssm_next_state(ssm);
|
|
|
|
break;
|
2008-03-19 17:46:59 +00:00
|
|
|
case INIT_FIX_FIRMWARE: ;
|
|
|
|
struct fpi_ssm *fwsm = fpi_ssm_new(dev->dev, fwfixer_run_state,
|
|
|
|
FWFIXER_NUM_STATES);
|
|
|
|
fwsm->priv = dev;
|
|
|
|
fpi_ssm_start_subsm(ssm, fwsm);
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case INIT_POWERUP: ;
|
|
|
|
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 (!IRQ_HANDLER_IS_RUNNING(urudev)) {
|
|
|
|
fpi_ssm_mark_aborted(ssm, -EIO);
|
2008-03-03 23:24:00 +00:00
|
|
|
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;
|
2007-11-16 17:25:41 +00:00
|
|
|
}
|
2008-03-03 23:24:00 +00:00
|
|
|
|
|
|
|
urudev->irq_cb_data = ssm;
|
|
|
|
urudev->irq_cb = init_scanpwr_irq_cb;
|
2008-02-17 00:08:56 +00:00
|
|
|
break;
|
|
|
|
case INIT_DONE:
|
2008-03-03 23:24:00 +00:00
|
|
|
fpi_timeout_cancel(urudev->scanpwr_irq_timeout);
|
|
|
|
urudev->scanpwr_irq_timeout = NULL;
|
2008-02-17 00:08:56 +00:00
|
|
|
urudev->irq_cb_data = NULL;
|
|
|
|
urudev->irq_cb = NULL;
|
|
|
|
fpi_ssm_mark_completed(ssm);
|
|
|
|
break;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
2008-02-17 00:08:56 +00:00
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void activate_initsm_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);
|
|
|
|
|
|
|
|
if (r) {
|
|
|
|
fpi_imgdev_activate_complete(dev, r);
|
|
|
|
return;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
r = dev_change_state(dev, urudev->activate_state);
|
|
|
|
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;
|
|
|
|
|
2008-03-03 23:24:00 +00:00
|
|
|
urudev->scanpwr_irq_timeouts = 0;
|
2008-02-17 00:08:56 +00:00
|
|
|
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 *****/
|
|
|
|
|
|
|
|
enum deinit_states {
|
|
|
|
DEINIT_SET_MODE_INIT = 0,
|
|
|
|
DEINIT_POWERDOWN,
|
|
|
|
DEINIT_NUM_STATES,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void deinit_run_state(struct fpi_ssm *ssm)
|
|
|
|
{
|
|
|
|
switch (ssm->cur_state) {
|
|
|
|
case DEINIT_SET_MODE_INIT:
|
|
|
|
sm_set_mode(ssm, MODE_INIT);
|
|
|
|
break;
|
|
|
|
case DEINIT_POWERDOWN:
|
|
|
|
sm_set_hwstat(ssm, 0x80);
|
|
|
|
break;
|
2007-11-04 19:01:15 +00:00
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void deactivate_irqs_stopped(struct fp_img_dev *dev)
|
|
|
|
{
|
|
|
|
fpi_imgdev_deactivate_complete(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void deactivate_deinitsm_complete(struct fpi_ssm *ssm)
|
|
|
|
{
|
|
|
|
struct fp_img_dev *dev = ssm->priv;
|
|
|
|
fpi_ssm_free(ssm);
|
|
|
|
stop_irq_handler(dev, deactivate_irqs_stopped);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dev_deactivate(struct fp_img_dev *dev)
|
|
|
|
{
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
|
|
|
struct fpi_ssm *ssm = fpi_ssm_new(dev->dev, deinit_run_state,
|
|
|
|
DEINIT_NUM_STATES);
|
|
|
|
|
|
|
|
stop_imaging_loop(dev);
|
|
|
|
urudev->irq_cb = NULL;
|
|
|
|
urudev->irq_cb_data = NULL;
|
|
|
|
ssm->priv = dev;
|
|
|
|
fpi_ssm_start(ssm, deactivate_deinitsm_complete);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***** LIBRARY STUFF *****/
|
|
|
|
|
2007-10-26 23:10:32 +00:00
|
|
|
static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
|
|
|
|
{
|
2008-05-12 22:24:05 +00:00
|
|
|
struct libusb_config_descriptor *config;
|
2008-04-06 22:55:41 +00:00
|
|
|
const struct libusb_interface *iface = NULL;
|
|
|
|
const struct libusb_interface_descriptor *iface_desc;
|
|
|
|
const struct libusb_endpoint_descriptor *ep;
|
2007-10-26 23:10:32 +00:00
|
|
|
struct uru4k_dev *urudev;
|
|
|
|
int i;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
/* Find fingerprint interface */
|
2008-05-12 22:24:05 +00:00
|
|
|
r = libusb_get_config_descriptor(libusb_get_device(dev->udev), 0, &config);
|
|
|
|
if (r < 0) {
|
|
|
|
fp_err("Failed to get config descriptor");
|
|
|
|
return r;
|
|
|
|
}
|
2007-10-26 23:10:32 +00:00
|
|
|
for (i = 0; i < config->bNumInterfaces; i++) {
|
2008-04-06 22:55:41 +00:00
|
|
|
const struct libusb_interface *cur_iface = &config->interface[i];
|
2007-10-26 23:10:32 +00:00
|
|
|
|
|
|
|
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");
|
2008-05-12 22:24:05 +00:00
|
|
|
r = -ENODEV;
|
|
|
|
goto out;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Find/check endpoints */
|
|
|
|
|
|
|
|
if (iface_desc->bNumEndpoints != 2) {
|
|
|
|
fp_err("found %d endpoints!?", iface_desc->bNumEndpoints);
|
2008-05-12 22:24:05 +00:00
|
|
|
r = -ENODEV;
|
|
|
|
goto out;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ep = &iface_desc->endpoint[0];
|
|
|
|
if (ep->bEndpointAddress != EP_INTR
|
2008-05-07 22:46:06 +00:00
|
|
|
|| (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
|
|
|
|
LIBUSB_TRANSFER_TYPE_INTERRUPT) {
|
2007-10-26 23:10:32 +00:00
|
|
|
fp_err("unrecognised interrupt endpoint");
|
2008-05-12 22:24:05 +00:00
|
|
|
r = -ENODEV;
|
|
|
|
goto out;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ep = &iface_desc->endpoint[1];
|
|
|
|
if (ep->bEndpointAddress != EP_DATA
|
2008-05-07 22:46:06 +00:00
|
|
|
|| (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
|
|
|
|
LIBUSB_TRANSFER_TYPE_BULK) {
|
2007-10-26 23:10:32 +00:00
|
|
|
fp_err("unrecognised bulk endpoint");
|
2008-05-12 22:24:05 +00:00
|
|
|
r = -ENODEV;
|
|
|
|
goto out;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Device looks like a supported reader */
|
|
|
|
|
2008-01-30 14:16:35 +00:00
|
|
|
r = libusb_claim_interface(dev->udev, iface_desc->bInterfaceNumber);
|
2007-10-26 23:10:32 +00:00
|
|
|
if (r < 0) {
|
|
|
|
fp_err("interface claim failed");
|
2008-05-12 22:24:05 +00:00
|
|
|
goto out;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
urudev = g_malloc0(sizeof(*urudev));
|
2007-11-04 21:12:48 +00:00
|
|
|
urudev->profile = &uru4k_dev_info[driver_data];
|
2007-10-26 23:10:32 +00:00
|
|
|
urudev->interface = iface_desc->bInterfaceNumber;
|
2007-11-16 17:25:41 +00:00
|
|
|
AES_set_encrypt_key(crkey, 128, &urudev->aeskey);
|
2007-10-26 23:10:32 +00:00
|
|
|
dev->priv = urudev;
|
2008-02-26 18:04:54 +00:00
|
|
|
fpi_imgdev_open_complete(dev, 0);
|
2008-05-12 22:24:05 +00:00
|
|
|
|
|
|
|
out:
|
|
|
|
libusb_free_config_descriptor(config);
|
|
|
|
return r;
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
2008-02-17 00:08:56 +00:00
|
|
|
static void dev_deinit(struct fp_img_dev *dev)
|
2007-10-26 23:10:32 +00:00
|
|
|
{
|
|
|
|
struct uru4k_dev *urudev = dev->priv;
|
2008-01-30 14:16:35 +00:00
|
|
|
libusb_release_interface(dev->udev, urudev->interface);
|
2007-10-26 23:10:32 +00:00
|
|
|
g_free(urudev);
|
2008-02-26 18:04:54 +00:00
|
|
|
fpi_imgdev_close_complete(dev);
|
2007-10-26 23:10:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 },
|
|
|
|
|
2007-11-16 17:25:41 +00:00
|
|
|
/* ms fp rdr (standalone) v2 */
|
|
|
|
{ .vendor = 0x045e, .product = 0x00ca, .driver_data = MS_STANDALONE_V2 },
|
|
|
|
|
2007-11-04 21:12:48 +00:00
|
|
|
/* dp uru4000 (standalone) */
|
|
|
|
{ .vendor = 0x05ba, .product = 0x0007, .driver_data = DP_URU4000 },
|
|
|
|
|
2008-05-02 10:55:50 +00:00
|
|
|
/* dp uru4000 (keyboard) */
|
|
|
|
{ .vendor = 0x05ba, .product = 0x0008, .driver_data = DP_URU4000 },
|
|
|
|
|
2007-10-26 23:10:32 +00:00
|
|
|
/* dp uru4000b (standalone) */
|
|
|
|
{ .vendor = 0x05ba, .product = 0x000a, .driver_data = DP_URU4000B },
|
|
|
|
|
|
|
|
/* terminating entry */
|
|
|
|
{ 0, 0, 0, },
|
|
|
|
};
|
|
|
|
|
|
|
|
struct fp_img_driver uru4000_driver = {
|
|
|
|
.driver = {
|
2007-10-28 22:02:04 +00:00
|
|
|
.id = 2,
|
2007-10-26 23:10:32 +00:00
|
|
|
.name = FP_COMPONENT,
|
|
|
|
.full_name = "Digital Persona U.are.U 4000/4000B",
|
|
|
|
.id_table = id_table,
|
2008-11-20 14:40:01 +00:00
|
|
|
.scan_type = FP_SCAN_TYPE_PRESS,
|
2007-10-26 23:10:32 +00:00
|
|
|
},
|
|
|
|
.flags = FP_IMGDRV_SUPPORTS_UNCONDITIONAL_CAPTURE,
|
2007-10-27 13:45:14 +00:00
|
|
|
.img_height = 289,
|
|
|
|
.img_width = 384,
|
|
|
|
|
2008-02-26 18:04:54 +00:00
|
|
|
.open = dev_init,
|
|
|
|
.close = dev_deinit,
|
2008-02-17 00:08:56 +00:00
|
|
|
.activate = dev_activate,
|
|
|
|
.deactivate = dev_deactivate,
|
|
|
|
.change_state = dev_change_state,
|
2007-10-26 23:10:32 +00:00
|
|
|
};
|
|
|
|
|