08f4be707c
The irq handler may already be stopped if stop_irq_handler is called. In that case, we should immediately call the handler rather than just never calling it. This fixes deactivation when the device is unexpectedly unplugged. Closes: #355
1464 lines
42 KiB
C
1464 lines
42 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 <nss.h>
|
|
#include <pk11pub.h>
|
|
|
|
#include "drivers_api.h"
|
|
|
|
#define EP_INTR (1 | FPI_USB_ENDPOINT_IN)
|
|
#define EP_DATA (2 | FPI_USB_ENDPOINT_IN)
|
|
#define USB_RQ 0x04
|
|
#define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | FPI_USB_ENDPOINT_IN)
|
|
#define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | FPI_USB_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
|
|
|
|
#define ENC_THRESHOLD 5000
|
|
|
|
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 image_not_flipped;
|
|
} 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,
|
|
.image_not_flipped = TRUE, /* See comment in the code where it is used. */
|
|
},
|
|
};
|
|
|
|
typedef void (*irq_cb_fn)(FpImageDevice *dev,
|
|
GError *error,
|
|
uint16_t type,
|
|
void *user_data);
|
|
typedef void (*irqs_stopped_cb_fn)(FpImageDevice *dev);
|
|
|
|
struct _FpiDeviceUru4000
|
|
{
|
|
FpImageDevice parent;
|
|
|
|
const struct uru4k_dev_profile *profile;
|
|
uint8_t interface;
|
|
FpiImageDeviceState activate_state;
|
|
unsigned char last_reg_rd[16];
|
|
unsigned char last_hwstat;
|
|
|
|
GCancellable *irq_cancellable;
|
|
FpiUsbTransfer *img_transfer;
|
|
void *img_data;
|
|
int img_data_actual_length;
|
|
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;
|
|
GSource *scanpwr_irq_timeout;
|
|
|
|
int fwfixer_offset;
|
|
unsigned char fwfixer_value;
|
|
|
|
CK_MECHANISM_TYPE cipher;
|
|
PK11SlotInfo *slot;
|
|
PK11SymKey *symkey;
|
|
SECItem *param;
|
|
};
|
|
G_DECLARE_FINAL_TYPE (FpiDeviceUru4000, fpi_device_uru4000, FPI, DEVICE_URU4000,
|
|
FpImageDevice);
|
|
G_DEFINE_TYPE (FpiDeviceUru4000, fpi_device_uru4000, FP_TYPE_IMAGE_DEVICE);
|
|
|
|
/* 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 *****/
|
|
|
|
static void
|
|
write_regs (FpImageDevice *dev, uint16_t first_reg,
|
|
uint16_t num_regs, unsigned char *values,
|
|
FpiUsbTransferCallback callback,
|
|
void *user_data)
|
|
{
|
|
FpiUsbTransfer *transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
|
|
|
|
transfer->short_is_error = TRUE;
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
USB_RQ, first_reg, 0,
|
|
num_regs);
|
|
memcpy (transfer->buffer, values, num_regs);
|
|
fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, callback, user_data);
|
|
}
|
|
|
|
static void
|
|
write_reg (FpImageDevice *dev, uint16_t reg,
|
|
unsigned char value,
|
|
FpiUsbTransferCallback callback,
|
|
void *user_data)
|
|
{
|
|
write_regs (dev, reg, 1, &value, callback, user_data);
|
|
}
|
|
|
|
static void
|
|
read_regs (FpImageDevice *dev, uint16_t first_reg,
|
|
uint16_t num_regs,
|
|
FpiUsbTransferCallback callback,
|
|
void *user_data)
|
|
{
|
|
FpiUsbTransfer *transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
|
|
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
USB_RQ, first_reg, 0, num_regs);
|
|
fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, callback, user_data);
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* an interrupt to the host. Maybe?
|
|
*/
|
|
|
|
static void
|
|
response_cb (FpiUsbTransfer *transfer, FpDevice *dev, void *user_data, GError *error)
|
|
{
|
|
/* NOTE: We could use the SSM function instead if we attached the ssm to the transfer! */
|
|
FpiSsm *ssm = user_data;
|
|
|
|
if (!error)
|
|
fpi_ssm_next_state (ssm);
|
|
else
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
}
|
|
|
|
static void
|
|
challenge_cb (FpiUsbTransfer *transfer, FpDevice *dev, void *user_data, GError *error)
|
|
{
|
|
FpiSsm *ssm = user_data;
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
unsigned char respdata[CR_LENGTH];
|
|
PK11Context *ctx;
|
|
int outlen;
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* submit response */
|
|
/* produce response from challenge */
|
|
ctx = PK11_CreateContextBySymKey (self->cipher, CKA_ENCRYPT,
|
|
self->symkey, self->param);
|
|
if (PK11_CipherOp (ctx, respdata, &outlen, CR_LENGTH, transfer->buffer, CR_LENGTH) != SECSuccess ||
|
|
PK11_Finalize (ctx) != SECSuccess)
|
|
{
|
|
fp_err ("Failed to encrypt challenge data");
|
|
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Failed to encrypt challenge data");
|
|
}
|
|
PK11_DestroyContext (ctx, PR_TRUE);
|
|
|
|
if (!error)
|
|
write_regs (FP_IMAGE_DEVICE (dev), REG_RESPONSE, CR_LENGTH, respdata, response_cb, ssm);
|
|
else
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
}
|
|
|
|
/*
|
|
* 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 (FpiSsm *ssm,
|
|
FpImageDevice *dev)
|
|
{
|
|
G_DEBUG_HERE ();
|
|
read_regs (dev, REG_CHALLENGE, CR_LENGTH, challenge_cb, ssm);
|
|
}
|
|
|
|
/***** INTERRUPT HANDLING *****/
|
|
|
|
#define IRQ_HANDLER_IS_RUNNING(urudev) ((urudev)->irq_cancellable)
|
|
|
|
static void start_irq_handler (FpImageDevice *dev);
|
|
|
|
static void
|
|
irq_handler (FpiUsbTransfer *transfer,
|
|
FpDevice *dev,
|
|
void *user_data,
|
|
GError *error)
|
|
{
|
|
FpImageDevice *imgdev = FP_IMAGE_DEVICE (dev);
|
|
FpiDeviceUru4000 *urudev = FPI_DEVICE_URU4000 (dev);
|
|
unsigned char *data = transfer->buffer;
|
|
uint16_t type;
|
|
|
|
g_clear_object (&urudev->irq_cancellable);
|
|
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
fp_dbg ("cancelled");
|
|
if (urudev->irqs_stopped_cb)
|
|
urudev->irqs_stopped_cb (imgdev);
|
|
urudev->irqs_stopped_cb = NULL;
|
|
return;
|
|
}
|
|
else if (error)
|
|
{
|
|
if (urudev->irq_cb)
|
|
{
|
|
urudev->irq_cb (imgdev, error, 0, urudev->irq_cb_data);
|
|
}
|
|
else
|
|
{
|
|
fp_dbg ("ignoring interrupt error: %s", error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
start_irq_handler (imgdev);
|
|
|
|
type = GUINT16_FROM_BE (*((uint16_t *) data));
|
|
fp_dbg ("recv irq type %04x", type);
|
|
|
|
/* 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 (imgdev, NULL, type, urudev->irq_cb_data);
|
|
else
|
|
fp_dbg ("ignoring interrupt");
|
|
}
|
|
|
|
static void
|
|
start_irq_handler (FpImageDevice *dev)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
FpiUsbTransfer *transfer;
|
|
|
|
g_assert (self->irq_cancellable == NULL);
|
|
self->irq_cancellable = g_cancellable_new ();
|
|
transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
|
|
transfer->ssm = NULL;
|
|
transfer->short_is_error = TRUE;
|
|
fpi_usb_transfer_fill_bulk (transfer,
|
|
EP_INTR,
|
|
IRQ_LENGTH);
|
|
fpi_usb_transfer_submit (transfer, 0, self->irq_cancellable, irq_handler, NULL);
|
|
}
|
|
|
|
static void
|
|
stop_irq_handler (FpImageDevice *dev, irqs_stopped_cb_fn cb)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
if (self->irq_cancellable)
|
|
{
|
|
g_cancellable_cancel (self->irq_cancellable);
|
|
self->irqs_stopped_cb = cb;
|
|
}
|
|
else
|
|
{
|
|
cb (dev);
|
|
}
|
|
}
|
|
|
|
/***** STATE CHANGING *****/
|
|
|
|
static void execute_state_change (FpImageDevice *dev);
|
|
|
|
static void
|
|
finger_presence_irq_cb (FpImageDevice *dev,
|
|
GError *error,
|
|
uint16_t type,
|
|
void *user_data)
|
|
{
|
|
if (error)
|
|
fpi_image_device_session_error (dev, error);
|
|
else if (type == IRQDATA_FINGER_ON)
|
|
fpi_image_device_report_finger_status (dev, TRUE);
|
|
else if (type == IRQDATA_FINGER_OFF)
|
|
fpi_image_device_report_finger_status (dev, FALSE);
|
|
else
|
|
fp_warn ("ignoring unexpected interrupt %04x", type);
|
|
}
|
|
|
|
static void
|
|
change_state_write_reg_cb (FpiUsbTransfer *transfer,
|
|
FpDevice *dev,
|
|
void *user_data,
|
|
GError *error)
|
|
{
|
|
if (error)
|
|
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
|
|
}
|
|
|
|
static void
|
|
dev_change_state (FpImageDevice *dev, FpiImageDeviceState state)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
self->activate_state = state;
|
|
if (self->img_transfer != NULL)
|
|
return;
|
|
|
|
execute_state_change (dev);
|
|
}
|
|
|
|
/***** GENERIC STATE MACHINE HELPER FUNCTIONS *****/
|
|
|
|
static void
|
|
sm_write_reg_cb (FpiUsbTransfer *transfer,
|
|
FpDevice *dev,
|
|
void *user_data,
|
|
GError *error)
|
|
{
|
|
FpiSsm *ssm = user_data;
|
|
|
|
if (error)
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
else
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
|
|
static void
|
|
sm_write_regs (FpiSsm *ssm,
|
|
FpImageDevice *dev,
|
|
uint16_t first_reg,
|
|
uint16_t num_regs,
|
|
void *data)
|
|
{
|
|
write_regs (dev, first_reg, num_regs, data, sm_write_reg_cb, ssm);
|
|
}
|
|
|
|
static void
|
|
sm_write_reg (FpiSsm *ssm,
|
|
FpImageDevice *dev,
|
|
uint16_t reg,
|
|
unsigned char value)
|
|
{
|
|
sm_write_regs (ssm, dev, reg, 1, &value);
|
|
}
|
|
|
|
static void
|
|
sm_read_reg_cb (FpiUsbTransfer *transfer,
|
|
FpDevice *dev,
|
|
void *user_data,
|
|
GError *error)
|
|
{
|
|
FpiSsm *ssm = user_data;
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
}
|
|
else
|
|
{
|
|
memcpy (self->last_reg_rd, transfer->buffer, transfer->actual_length);
|
|
fp_dbg ("reg value %x", self->last_reg_rd[0]);
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
}
|
|
|
|
#define member_size(type, member) sizeof (((type *) 0)->member)
|
|
|
|
static void
|
|
sm_read_regs (FpiSsm *ssm,
|
|
FpImageDevice *dev,
|
|
uint16_t reg,
|
|
uint16_t num_regs)
|
|
{
|
|
g_assert (num_regs <= member_size (FpiDeviceUru4000, last_reg_rd));
|
|
|
|
fp_dbg ("read %d regs at %x", num_regs, reg);
|
|
read_regs (dev, reg, num_regs, sm_read_reg_cb, ssm);
|
|
}
|
|
|
|
static void
|
|
sm_read_reg (FpiSsm *ssm,
|
|
FpImageDevice *dev,
|
|
uint16_t reg)
|
|
{
|
|
sm_read_regs (ssm, dev, reg, 1);
|
|
}
|
|
|
|
static void
|
|
sm_set_hwstat (FpiSsm *ssm,
|
|
FpImageDevice *dev,
|
|
unsigned char value)
|
|
{
|
|
fp_dbg ("set %02x", value);
|
|
sm_write_reg (ssm, dev, REG_HWSTAT, value);
|
|
}
|
|
|
|
/***** IMAGING LOOP *****/
|
|
|
|
enum imaging_states {
|
|
IMAGING_CAPTURE,
|
|
IMAGING_SEND_INDEX,
|
|
IMAGING_READ_KEY,
|
|
IMAGING_DECODE,
|
|
IMAGING_REPORT_IMAGE,
|
|
IMAGING_NUM_STATES
|
|
};
|
|
|
|
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 void
|
|
image_transfer_cb (FpiUsbTransfer *transfer, FpDevice *dev,
|
|
gpointer user_data, GError *error)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
FpiSsm *ssm = transfer->ssm;
|
|
|
|
if (error)
|
|
{
|
|
fp_dbg ("error");
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
}
|
|
else
|
|
{
|
|
self->img_data = g_memdup (transfer->buffer, sizeof (struct uru4k_image));
|
|
self->img_data_actual_length = transfer->actual_length;
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
}
|
|
|
|
enum {
|
|
BLOCKF_CHANGE_KEY = 0x80,
|
|
BLOCKF_NO_KEY_UPDATE = 0x04,
|
|
BLOCKF_ENCRYPTED = 0x02,
|
|
BLOCKF_NOT_PRESENT = 0x01,
|
|
};
|
|
|
|
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 implicitly zero */
|
|
data[i] = 0;
|
|
return update_key (key);
|
|
}
|
|
|
|
static int
|
|
calc_dev2 (struct uru4k_image *img)
|
|
{
|
|
uint8_t *b[2] = { NULL, NULL };
|
|
int res = 0, mean = 0, i, r, j, idx;
|
|
|
|
for (i = r = idx = 0; i < G_N_ELEMENTS (img->block_info) && idx < 2; i++)
|
|
{
|
|
if (img->block_info[i].flags & BLOCKF_NOT_PRESENT)
|
|
continue;
|
|
for (j = 0; j < img->block_info[i].num_lines && idx < 2; j++)
|
|
b[idx++] = img->data[r++];
|
|
}
|
|
if (!b[0] || !b[1])
|
|
{
|
|
fp_dbg ("NULL! %p %p", b[0], b[1]);
|
|
return 0;
|
|
}
|
|
for (i = 0; i < IMAGE_WIDTH; i++)
|
|
mean += (int) b[0][i] + (int) b[1][i];
|
|
|
|
mean /= IMAGE_WIDTH;
|
|
|
|
for (i = 0; i < IMAGE_WIDTH; i++)
|
|
{
|
|
int dev = (int) b[0][i] + (int) b[1][i] - mean;
|
|
res += dev * dev;
|
|
}
|
|
|
|
return res / IMAGE_WIDTH;
|
|
}
|
|
|
|
static void
|
|
imaging_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);
|
|
struct uru4k_image *img = self->img_data;
|
|
FpImage *fpimg;
|
|
uint32_t key;
|
|
uint8_t flags, num_lines;
|
|
int i, r, to, dev2;
|
|
unsigned char buf[5];
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case IMAGING_CAPTURE:
|
|
self->img_lines_done = 0;
|
|
self->img_block = 0;
|
|
fpi_usb_transfer_submit (fpi_usb_transfer_ref (self->img_transfer),
|
|
0,
|
|
NULL,
|
|
image_transfer_cb,
|
|
NULL);
|
|
|
|
break;
|
|
|
|
case IMAGING_SEND_INDEX:
|
|
fp_dbg ("hw header lines %d", img->num_lines);
|
|
|
|
if (img->num_lines >= IMAGE_HEIGHT ||
|
|
self->img_data_actual_length < img->num_lines * IMAGE_WIDTH + 64)
|
|
{
|
|
fp_err ("bad captured image (%d lines) or size mismatch %d < %d",
|
|
img->num_lines,
|
|
self->img_data_actual_length,
|
|
img->num_lines * IMAGE_WIDTH + 64);
|
|
fpi_ssm_jump_to_state (ssm, IMAGING_CAPTURE);
|
|
return;
|
|
}
|
|
|
|
/* Detect whether image is encrypted (by checking how noisy it is) */
|
|
dev2 = calc_dev2 (img);
|
|
fp_dbg ("dev2: %d", dev2);
|
|
if (dev2 < ENC_THRESHOLD)
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, IMAGING_REPORT_IMAGE);
|
|
return;
|
|
}
|
|
fp_info ("image seems to be encrypted");
|
|
|
|
buf[0] = img->key_number;
|
|
buf[1] = self->img_enc_seed;
|
|
buf[2] = self->img_enc_seed >> 8;
|
|
buf[3] = self->img_enc_seed >> 16;
|
|
buf[4] = self->img_enc_seed >> 24;
|
|
sm_write_regs (ssm, dev, REG_SCRAMBLE_DATA_INDEX, 5, buf);
|
|
break;
|
|
|
|
case IMAGING_READ_KEY:
|
|
sm_read_regs (ssm, dev, REG_SCRAMBLE_DATA_KEY, 4);
|
|
break;
|
|
|
|
case IMAGING_DECODE:
|
|
key = self->last_reg_rd[0];
|
|
key |= self->last_reg_rd[1] << 8;
|
|
key |= self->last_reg_rd[2] << 16;
|
|
key |= self->last_reg_rd[3] << 24;
|
|
key ^= self->img_enc_seed;
|
|
|
|
fp_dbg ("encryption id %02x -> key %08x", img->key_number, key);
|
|
while (self->img_block < G_N_ELEMENTS (img->block_info) &&
|
|
self->img_lines_done < img->num_lines)
|
|
{
|
|
flags = img->block_info[self->img_block].flags;
|
|
num_lines = img->block_info[self->img_block].num_lines;
|
|
if (num_lines == 0)
|
|
break;
|
|
|
|
fp_dbg ("%d %02x %d", self->img_block, flags,
|
|
num_lines);
|
|
if (flags & BLOCKF_CHANGE_KEY)
|
|
{
|
|
fp_dbg ("changing encryption keys.");
|
|
img->block_info[self->img_block].flags &= ~BLOCKF_CHANGE_KEY;
|
|
img->key_number++;
|
|
self->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[self->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)
|
|
self->img_lines_done += num_lines;
|
|
self->img_block++;
|
|
}
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case IMAGING_REPORT_IMAGE:
|
|
fpimg = fp_image_new (IMAGE_WIDTH, IMAGE_HEIGHT);
|
|
|
|
to = r = 0;
|
|
for (i = 0; i < G_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 = FPI_IMAGE_COLORS_INVERTED;
|
|
/* NOTE: For some reason all but U4000B (or rather U4500?) flipped the
|
|
* image, we retain this behaviour here, but it is not clear whether it
|
|
* is correct.
|
|
* It may be that there are different models with the same USB ID that
|
|
* behave differently.
|
|
*/
|
|
if (self->profile->image_not_flipped)
|
|
fpimg->flags |= FPI_IMAGE_V_FLIPPED | FPI_IMAGE_H_FLIPPED;
|
|
fpi_image_device_image_captured (dev, fpimg);
|
|
|
|
if (self->activate_state == FPI_IMAGE_DEVICE_STATE_CAPTURE)
|
|
fpi_ssm_jump_to_state (ssm, IMAGING_CAPTURE);
|
|
else
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
imaging_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
|
|
/* Report error before exiting imaging loop - the error handler
|
|
* can request state change, which needs to be postponed to end of
|
|
* this function. */
|
|
if (error)
|
|
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
|
|
|
|
g_clear_pointer (&self->img_transfer, fpi_usb_transfer_unref);
|
|
|
|
g_free (self->img_data);
|
|
self->img_data = NULL;
|
|
self->img_data_actual_length = 0;
|
|
|
|
execute_state_change (FP_IMAGE_DEVICE (dev));
|
|
}
|
|
|
|
/***** 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_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case REBOOTPWR_SET_HWSTAT:
|
|
self->rebootpwr_ctr = 100;
|
|
sm_set_hwstat (ssm, dev, self->last_hwstat & 0xf);
|
|
break;
|
|
|
|
case REBOOTPWR_GET_HWSTAT:
|
|
sm_read_reg (ssm, dev, REG_HWSTAT);
|
|
break;
|
|
|
|
case REBOOTPWR_CHECK_HWSTAT:
|
|
self->last_hwstat = self->last_reg_rd[0];
|
|
if (self->last_hwstat & 0x1)
|
|
fpi_ssm_mark_completed (ssm);
|
|
else
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case REBOOTPWR_PAUSE:
|
|
if (!--self->rebootpwr_ctr)
|
|
{
|
|
fp_err ("could not reboot device power");
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR,
|
|
"Could not reboot device"));
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_jump_to_state_delayed (ssm, 10, REBOOTPWR_GET_HWSTAT, NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* After messing with the device firmware in its 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 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_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case POWERUP_INIT:
|
|
self->powerup_ctr = 100;
|
|
self->powerup_hwstat = self->last_hwstat & 0xf;
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case POWERUP_SET_HWSTAT:
|
|
sm_set_hwstat (ssm, dev, self->powerup_hwstat);
|
|
break;
|
|
|
|
case POWERUP_GET_HWSTAT:
|
|
sm_read_reg (ssm, dev, REG_HWSTAT);
|
|
break;
|
|
|
|
case POWERUP_CHECK_HWSTAT:
|
|
self->last_hwstat = self->last_reg_rd[0];
|
|
if ((self->last_reg_rd[0] & 0x80) == 0)
|
|
fpi_ssm_mark_completed (ssm);
|
|
else
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case POWERUP_PAUSE:
|
|
if (!--self->powerup_ctr)
|
|
{
|
|
fp_err ("could not power device up");
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"could not power device up"));
|
|
}
|
|
else if (!self->profile->auth_cr)
|
|
{
|
|
fpi_ssm_jump_to_state_delayed (ssm, POWERUP_SET_HWSTAT, 10, NULL);
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_next_state_delayed (ssm, 10, NULL);
|
|
}
|
|
break;
|
|
|
|
case POWERUP_CHALLENGE_RESPONSE:
|
|
sm_do_challenge_response (ssm, dev);
|
|
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 (FpImageDevice *dev, GError *error,
|
|
uint16_t type, void *user_data)
|
|
{
|
|
FpiSsm *ssm = user_data;
|
|
FpiDeviceUru4000 *urudev = FPI_DEVICE_URU4000 (dev);
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
}
|
|
else if (type != IRQDATA_SCANPWR_ON)
|
|
{
|
|
fp_dbg ("ignoring interrupt");
|
|
}
|
|
else if (fpi_ssm_get_cur_state (ssm) != 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 (FpDevice *dev,
|
|
void *user_data)
|
|
{
|
|
FpiSsm *ssm = user_data;
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
fp_warn ("powerup timed out");
|
|
self->irq_cb = NULL;
|
|
self->scanpwr_irq_timeout = NULL;
|
|
|
|
if (++self->scanpwr_irq_timeouts >= 3)
|
|
{
|
|
fp_err ("powerup timed out 3 times, giving up");
|
|
fpi_ssm_mark_failed (ssm,
|
|
g_error_new_literal (G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_TIMED_OUT,
|
|
"Powerup timed out 3 times, giving up"));
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, INIT_GET_HWSTAT);
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (_dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case INIT_GET_HWSTAT:
|
|
sm_read_reg (ssm, dev, REG_HWSTAT);
|
|
break;
|
|
|
|
case INIT_CHECK_HWSTAT_REBOOT:
|
|
self->last_hwstat = self->last_reg_rd[0];
|
|
if ((self->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:;
|
|
FpiSsm *rebootsm = fpi_ssm_new (FP_DEVICE (dev),
|
|
rebootpwr_run_state,
|
|
REBOOTPWR_NUM_STATES);
|
|
fpi_ssm_start_subsm (ssm, rebootsm);
|
|
break;
|
|
|
|
case INIT_CHECK_HWSTAT_POWERDOWN:
|
|
if ((self->last_hwstat & 0x80) == 0)
|
|
sm_set_hwstat (ssm, dev, self->last_hwstat | 0x80);
|
|
else
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case INIT_POWERUP:
|
|
if (!IRQ_HANDLER_IS_RUNNING (self))
|
|
{
|
|
fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"IRQ handler should be running but is not"));
|
|
return;
|
|
}
|
|
self->irq_cb_data = ssm;
|
|
self->irq_cb = init_scanpwr_irq_cb;
|
|
|
|
FpiSsm *powerupsm = fpi_ssm_new (FP_DEVICE (dev),
|
|
powerup_run_state,
|
|
POWERUP_NUM_STATES);
|
|
fpi_ssm_start_subsm (ssm, powerupsm);
|
|
break;
|
|
|
|
case INIT_AWAIT_SCAN_POWER:
|
|
if (self->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. */
|
|
self->scanpwr_irq_timeout = fpi_device_add_timeout (_dev,
|
|
300,
|
|
init_scanpwr_timeout,
|
|
ssm, NULL);
|
|
break;
|
|
|
|
case INIT_DONE:
|
|
if (self->scanpwr_irq_timeout)
|
|
{
|
|
g_source_destroy (self->scanpwr_irq_timeout);
|
|
self->scanpwr_irq_timeout = NULL;
|
|
}
|
|
self->irq_cb_data = NULL;
|
|
self->irq_cb = NULL;
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case INIT_GET_VERSION:
|
|
sm_read_regs (ssm, dev, 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",
|
|
self->last_reg_rd[10], self->last_reg_rd[11],
|
|
self->last_reg_rd[4], self->last_reg_rd[5]);
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
activate_initsm_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
|
|
{
|
|
fpi_image_device_activate_complete (FP_IMAGE_DEVICE (dev), error);
|
|
}
|
|
|
|
static void
|
|
dev_activate (FpImageDevice *dev)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
FpiSsm *ssm;
|
|
|
|
start_irq_handler (dev);
|
|
|
|
self->scanpwr_irq_timeouts = 0;
|
|
ssm = fpi_ssm_new (FP_DEVICE (dev), init_run_state, INIT_NUM_STATES);
|
|
fpi_ssm_start (ssm, activate_initsm_complete);
|
|
}
|
|
|
|
/***** DEINITIALIZATION *****/
|
|
|
|
static void
|
|
deactivate_irqs_stopped (FpImageDevice *dev)
|
|
{
|
|
fpi_image_device_deactivate_complete (dev, NULL);
|
|
}
|
|
|
|
static void
|
|
deactivate_write_reg_cb (FpiUsbTransfer *transfer, FpDevice *dev,
|
|
gpointer user_data, GError *error)
|
|
{
|
|
stop_irq_handler (FP_IMAGE_DEVICE (dev), deactivate_irqs_stopped);
|
|
}
|
|
|
|
static void
|
|
dev_deactivate (FpImageDevice *dev)
|
|
{
|
|
/* This is started/handled by execute_state_change in order to delay the
|
|
* action until after the image transfer has completed.
|
|
* We just need to override the function so that the complete handler is
|
|
* not called automatically. */
|
|
}
|
|
|
|
static void
|
|
execute_state_change (FpImageDevice *dev)
|
|
{
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
FpiSsm *ssm;
|
|
|
|
switch (self->activate_state)
|
|
{
|
|
case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
|
|
fp_dbg ("deactivating");
|
|
self->irq_cb = NULL;
|
|
self->irq_cb_data = NULL;
|
|
write_reg (dev, REG_MODE, MODE_OFF,
|
|
deactivate_write_reg_cb, NULL);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
|
|
fp_dbg ("wait finger on");
|
|
if (!IRQ_HANDLER_IS_RUNNING (self))
|
|
{
|
|
fpi_image_device_session_error (dev,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"IRQ handler should be running but is not"));
|
|
return;
|
|
}
|
|
self->irq_cb = finger_presence_irq_cb;
|
|
write_reg (dev, REG_MODE, MODE_AWAIT_FINGER_ON,
|
|
change_state_write_reg_cb, NULL);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_CAPTURE:
|
|
fp_dbg ("starting capture");
|
|
self->irq_cb = NULL;
|
|
|
|
ssm = fpi_ssm_new (FP_DEVICE (dev), imaging_run_state,
|
|
IMAGING_NUM_STATES);
|
|
self->img_enc_seed = rand ();
|
|
self->img_transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
|
|
self->img_transfer->ssm = ssm;
|
|
self->img_transfer->short_is_error = FALSE;
|
|
fpi_usb_transfer_fill_bulk (self->img_transfer,
|
|
EP_DATA,
|
|
sizeof (struct uru4k_image));
|
|
|
|
fpi_ssm_start (ssm, imaging_complete);
|
|
|
|
write_reg (dev, REG_MODE, MODE_CAPTURE,
|
|
change_state_write_reg_cb, NULL);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
|
|
fp_dbg ("await finger off");
|
|
if (!IRQ_HANDLER_IS_RUNNING (self))
|
|
{
|
|
fpi_image_device_session_error (dev,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"IRQ handler should be running but is not"));
|
|
return;
|
|
}
|
|
self->irq_cb = finger_presence_irq_cb;
|
|
write_reg (dev, REG_MODE, MODE_AWAIT_FINGER_OFF,
|
|
change_state_write_reg_cb, NULL);
|
|
break;
|
|
|
|
/* Ignored states */
|
|
case FPI_IMAGE_DEVICE_STATE_IDLE:
|
|
case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
|
|
case FPI_IMAGE_DEVICE_STATE_INACTIVE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***** LIBRARY STUFF *****/
|
|
|
|
static void
|
|
dev_init (FpImageDevice *dev)
|
|
{
|
|
GError *error = NULL;
|
|
FpiDeviceUru4000 *self;
|
|
|
|
g_autoptr(GPtrArray) interfaces = NULL;
|
|
GUsbInterface *iface = NULL;
|
|
guint64 driver_data;
|
|
SECStatus rv;
|
|
SECItem item;
|
|
int i;
|
|
|
|
interfaces = g_usb_device_get_interfaces (fpi_device_get_usb_device (FP_DEVICE (dev)), &error);
|
|
if (error)
|
|
{
|
|
fpi_image_device_open_complete (dev, error);
|
|
return;
|
|
}
|
|
|
|
/* Find fingerprint interface; TODO: Move this into probe() */
|
|
for (i = 0; i < interfaces->len; i++)
|
|
{
|
|
GUsbInterface *cur_iface = g_ptr_array_index (interfaces, i);
|
|
|
|
if (g_usb_interface_get_class (cur_iface) == 255 &&
|
|
g_usb_interface_get_subclass (cur_iface) == 255 &&
|
|
g_usb_interface_get_protocol (cur_iface) == 255)
|
|
{
|
|
iface = cur_iface;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iface == NULL)
|
|
{
|
|
fp_err ("could not find interface");
|
|
fpi_image_device_open_complete (dev,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"Could not find interface"));
|
|
return;
|
|
}
|
|
|
|
/* TODO: Find/check endpoints; does not seem easily possible with GUsb unfortunately! */
|
|
#if 0
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
/* Device looks like a supported reader */
|
|
|
|
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
|
|
g_usb_interface_get_number (iface), 0, &error))
|
|
{
|
|
fpi_image_device_open_complete (dev, error);
|
|
return;
|
|
}
|
|
|
|
/* Disable loading p11-kit's user configuration */
|
|
g_setenv ("P11_KIT_NO_USER_CONFIG", "1", TRUE);
|
|
|
|
/* Initialise NSS early */
|
|
rv = NSS_NoDB_Init (".");
|
|
if (rv != SECSuccess)
|
|
{
|
|
fp_err ("could not initialise NSS");
|
|
fpi_image_device_open_complete (dev,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"Could not initialise NSS"));
|
|
return;
|
|
}
|
|
|
|
self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
driver_data = fpi_device_get_driver_data (FP_DEVICE (dev));
|
|
self->profile = &uru4k_dev_info[driver_data];
|
|
self->interface = g_usb_interface_get_number (iface);
|
|
|
|
/* Set up encryption */
|
|
self->cipher = CKM_AES_ECB;
|
|
self->slot = PK11_GetBestSlot (self->cipher, NULL);
|
|
if (self->slot == NULL)
|
|
{
|
|
fp_err ("could not get encryption slot");
|
|
fpi_image_device_open_complete (dev,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"Could not get encryption slot"));
|
|
return;
|
|
}
|
|
item.type = siBuffer;
|
|
item.data = (unsigned char *) crkey;
|
|
item.len = sizeof (crkey);
|
|
self->symkey = PK11_ImportSymKey (self->slot,
|
|
self->cipher,
|
|
PK11_OriginUnwrap,
|
|
CKA_ENCRYPT,
|
|
&item, NULL);
|
|
if (self->symkey == NULL)
|
|
{
|
|
fp_err ("failed to import key into NSS");
|
|
PK11_FreeSlot (self->slot);
|
|
self->slot = NULL;
|
|
fpi_image_device_open_complete (dev,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
|
|
"Failed to import key into NSS"));
|
|
return;
|
|
}
|
|
self->param = PK11_ParamFromIV (self->cipher, NULL);
|
|
|
|
fpi_image_device_open_complete (dev, NULL);
|
|
}
|
|
|
|
static void
|
|
dev_deinit (FpImageDevice *dev)
|
|
{
|
|
GError *error = NULL;
|
|
FpiDeviceUru4000 *self = FPI_DEVICE_URU4000 (dev);
|
|
|
|
if (self->symkey)
|
|
PK11_FreeSymKey (self->symkey);
|
|
if (self->param)
|
|
SECITEM_FreeItem (self->param, PR_TRUE);
|
|
if (self->slot)
|
|
PK11_FreeSlot (self->slot);
|
|
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
|
|
self->interface, 0, &error);
|
|
fpi_image_device_close_complete (dev, error);
|
|
}
|
|
|
|
static const FpIdEntry id_table[] = {
|
|
/* ms kbd with fp rdr */
|
|
{ .vid = 0x045e, .pid = 0x00bb, .driver_data = MS_KBD },
|
|
|
|
/* ms intellimouse with fp rdr */
|
|
{ .vid = 0x045e, .pid = 0x00bc, .driver_data = MS_INTELLIMOUSE },
|
|
|
|
/* ms fp rdr (standalone) */
|
|
{ .vid = 0x045e, .pid = 0x00bd, .driver_data = MS_STANDALONE },
|
|
|
|
/* ms fp rdr (standalone) v2 */
|
|
{ .vid = 0x045e, .pid = 0x00ca, .driver_data = MS_STANDALONE_V2 },
|
|
|
|
/* dp uru4000 (standalone) */
|
|
{ .vid = 0x05ba, .pid = 0x0007, .driver_data = DP_URU4000 },
|
|
|
|
/* dp uru4000 (keyboard) */
|
|
{ .vid = 0x05ba, .pid = 0x0008, .driver_data = DP_URU4000 },
|
|
|
|
/* dp uru4000b (standalone) */
|
|
{ .vid = 0x05ba, .pid = 0x000a, .driver_data = DP_URU4000B },
|
|
|
|
/* terminating entry */
|
|
{ .vid = 0, .pid = 0, .driver_data = 0 },
|
|
};
|
|
|
|
static void
|
|
fpi_device_uru4000_init (FpiDeviceUru4000 *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fpi_device_uru4000_class_init (FpiDeviceUru4000Class *klass)
|
|
{
|
|
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
|
|
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
|
|
|
|
dev_class->id = "uru4000";
|
|
dev_class->full_name = "Digital Persona U.are.U 4000/4000B/4500";
|
|
dev_class->type = FP_DEVICE_TYPE_USB;
|
|
dev_class->id_table = id_table;
|
|
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
|
|
|
|
img_class->img_open = dev_init;
|
|
img_class->img_close = dev_deinit;
|
|
img_class->activate = dev_activate;
|
|
img_class->deactivate = dev_deactivate;
|
|
img_class->change_state = dev_change_state;
|
|
|
|
img_class->img_width = IMAGE_WIDTH;
|
|
img_class->img_height = IMAGE_HEIGHT;
|
|
}
|