2
* ThinkFinger - A driver for the UPEK/SGS Thomson Microelectronics
5
* Copyright (C) 2006 Pavel Machek <pavel@suse.cz>
6
* Timo Hoenig <thoenig@suse.de>
8
* Copyright (C) 2007 Timo Hoenig <thoenig@suse.de>
10
* This program is free software; you can redistribute it and/or modify
11
* it under the terms of the GNU General Public License as published by
12
* the Free Software Foundation; either version 2 of the License, or
13
* (at your option) any later version.
15
* This program is distributed in the hope that it will be useful,
16
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
* GNU General Public License for more details.
20
* You should have received a copy of the GNU General Public License
21
* along with this program; if not, write to the
22
* Free Software Foundation, Inc.,
23
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
* TODO: move this to documentation
26
* Hardware should be 248 x 4 pixels, 8bit per pixel, but seems to do matching
27
* completely in hardware.
29
* TODO: this is not true for all distributions
30
* Note that you need to be root to use this.
33
#include "libthinkfinger.h"
34
#include "libthinkfinger-crc.h"
36
#define USB_VENDOR_ID 0x0483
37
#define USB_PRODUCT_ID 0x2016
38
#define USB_TIMEOUT 5000
39
#define USB_WR_EP 0x02
40
#define USB_RD_EP 0x81
41
#define DEFAULT_BULK_SIZE 0x40
42
#define INITIAL_SEQUENCE 0x60
44
static char init_a[17] = {
45
0x43, 0x69, 0x61, 0x6f, 0x04, 0x00, 0x08, 0x01,
46
0x00, 0xe8, 0x03, 0x00, 0x00, 0xff, 0x07, 0xdb,
50
static char init_b[16] = {
51
0x43, 0x69, 0x61, 0x6f, 0x00, 0x00, 0x07, 0x28,
52
0x04, 0x00, 0x00, 0x00, 0x06, 0x04, 0xc0, 0xd6
55
static char init_c[16] = {
56
0x43, 0x69, 0x61, 0x6f, 0x00, 0x10, 0x07, 0x28,
57
0x04, 0x00, 0x00, 0x00, 0x07, 0x04, 0x0f, 0xb6
61
static char init_d[40] = {
62
0x43, 0x69, 0x61, 0x6f, 0x00, 0x20, 0x1f, 0x28,
63
0x1c, 0x00, 0x00, 0x00, 0x08, 0x04, 0x83, 0x00,
64
0x2c, 0x22, 0x23, 0x97, 0xc9, 0xa7, 0x15, 0xa0,
65
0x8a, 0xab, 0x3c, 0xd0, 0xbf, 0xdb, 0xf3, 0x92,
66
0x6f, 0xae, 0x3b, 0x1e, 0x44, 0xc4, 0x9a, 0x45
69
static char init_e[20] = {
70
0x43, 0x69, 0x61, 0x6f, 0x00, 0x30, 0x0b, 0x28,
71
0x08, 0x00, 0x00, 0x00, 0x0c, 0x04, 0x03, 0x00,
72
0x00, 0x00, 0x6d, 0x7e
76
static char init_end[120] = {
77
0x43, 0x69, 0x61, 0x6f, 0x00, 0x40, 0x6f, 0x28,
78
0x6c, 0x00, 0x00, 0x00, 0x0b, 0x04, 0x03, 0x00,
79
0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00,
80
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
81
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
82
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
83
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01,
84
0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00,
85
0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
86
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
87
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
88
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00,
89
0x0a, 0x00, 0x64, 0x00, 0xf4, 0x01, 0x32, 0x00,
90
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
91
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0xd6, 0x66
94
static char deinit[10] = {
95
0x43, 0x69, 0x61, 0x6f, 0x07, 0x00, 0x01, 0x00,
99
static char device_busy[9] = {
100
0x43, 0x69, 0x61, 0x6f, 0x09, 0x00, 0x00, 0x91,
109
static struct init_table init[] = {
110
{ init_a, sizeof (init_a) },
111
{ init_b, sizeof (init_b) },
112
{ init_c, sizeof (init_c) },
113
{ init_d, sizeof (init_d) },
114
{ init_e, sizeof (init_e) },
118
static char ctrlbuf[1024] = {
119
0x43, 0x69, 0x61, 0x6f, 0x00, 0x51, 0x0b, 0x28,
120
0xb8, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00,
121
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
122
0x00, 0x00, 0xc0, 0xd4, 0x01, 0x00, 0x20, 0x00,
126
static char enroll_init[23] = {
127
0x43, 0x69, 0x61, 0x6f, 0x00, 0x50, 0x0e, 0x28,
128
0x0b, 0x00, 0x00, 0x00, 0x02, 0x02, 0xc0, 0xd4,
129
0x01, 0x00, 0x04, 0x00, 0x08, 0x0f, 0x86
132
static unsigned char scan_sequence[17] = {
133
0x43, 0x69, 0x61, 0x6f, 0x00, 0xff, 0x08, 0x28,
134
0x05, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0xff,
138
static unsigned char termination_request = 0x01;
140
struct libthinkfinger_s {
141
struct sigaction sigint_action;
142
struct sigaction sigint_action_old;
143
struct usb_dev_handle *usb_dev_handle;
147
pthread_mutex_t usb_deinit_mutex;
148
libthinkfinger_task task;
150
_Bool result_pending;
151
unsigned char next_sequence;
153
libthinkfinger_state state;
154
libthinkfinger_state_cb cb;
158
static void sigint_handler (int unused, siginfo_t *sinfo, void *data) {
159
termination_request = 0x00;
163
static int _libthinkfinger_set_sigint (libthinkfinger *tf)
167
tf->sigint_action.sa_sigaction = &sigint_handler;
168
retval = sigaction (SIGINT, &tf->sigint_action, &tf->sigint_action_old);
173
static int _libthinkfinger_restore_sigint (libthinkfinger *tf)
177
retval = sigaction(SIGINT, &tf->sigint_action_old, NULL);
182
static _Bool _libthinkfinger_result_pending (libthinkfinger *tf)
184
return tf->result_pending;
187
static void _libthinkfinger_set_result_pending (libthinkfinger *tf, _Bool pending)
189
tf->result_pending = pending;
192
static void _libthinkfinger_task_start (libthinkfinger *tf, libthinkfinger_task task)
195
tf->state = TF_STATE_INITIAL;
196
tf->task_running = true;
199
static void _libthinkfinger_task_stop (libthinkfinger *tf)
201
tf->task_running = false;
202
tf->task = TF_TASK_IDLE;
205
static _Bool _libthinkfinger_task_running (libthinkfinger *tf)
207
return tf->task_running;
210
static libthinkfinger_result _libthinkfinger_get_result (libthinkfinger_state state)
212
libthinkfinger_result retval;
214
case TF_STATE_ACQUIRE_SUCCESS:
215
retval = TF_RESULT_ACQUIRE_SUCCESS;
217
case TF_STATE_ACQUIRE_FAILED:
218
retval = TF_RESULT_ACQUIRE_FAILED;
220
case TF_STATE_VERIFY_SUCCESS:
221
retval = TF_RESULT_VERIFY_SUCCESS;
223
case TF_STATE_VERIFY_FAILED:
224
retval = TF_RESULT_VERIFY_FAILED;
226
case TF_STATE_OPEN_FAILED:
227
retval = TF_RESULT_OPEN_FAILED;
229
case TF_STATE_SIGINT:
230
retval = TF_RESULT_SIGINT;
232
case TF_STATE_USB_ERROR:
233
retval = TF_RESULT_USB_ERROR;
235
case TF_STATE_COMM_FAILED:
236
retval = TF_RESULT_COMM_FAILED;
239
retval = TF_RESULT_UNDEFINED;
247
static void usb_dump (const char *func, unsigned char *bytes, int req_size, int size)
250
fprintf (stderr, "\n%s\t(0x%x/0x%x): ", func, req_size, size);
252
fprintf(stderr, "%2.2x", *bytes);
255
fprintf (stderr, "\n");
257
fprintf (stderr, "Error: %s (%i)\n", func, size);
263
static int _libthinkfinger_usb_hello (struct usb_dev_handle *handle)
266
char dummy[] = "\x10";
268
/* SET_CONFIGURATION 1 -- should not be relevant */
269
retval = usb_control_msg (handle, // usb_dev_handle *dev
270
0x00000000, // int requesttype
271
0x00000009, // int request
274
dummy, // char *bytes
275
0x00000000, // int size
276
USB_TIMEOUT); // int timeout
279
retval = usb_control_msg (handle, // usb_dev_handle *dev
280
0x00000040, // int requesttype
281
0x0000000c, // int request
284
dummy, // char *bytes
285
0x00000001, // int size
286
USB_TIMEOUT); // int timeout
292
static int _libthinkfinger_usb_write (libthinkfinger *tf, char *bytes, int size) {
295
if (tf->usb_dev_handle == NULL) {
297
fprintf (stderr, "_libthinkfinger_usb_write error: USB handle is NULL.\n");
302
usb_retval = usb_bulk_write (tf->usb_dev_handle, USB_WR_EP, bytes, size, USB_TIMEOUT);
303
if (usb_retval >= 0 && usb_retval != size)
304
fprintf (stderr, "Warning: usb_bulk_write expected to write 0x%x (wrote 0x%x bytes).\n",
308
usb_dump ("usb_bulk_write", (unsigned char*) bytes, size, usb_retval);
314
static int _libthinkfinger_usb_read (libthinkfinger *tf, char *bytes, int size) {
317
if (tf->usb_dev_handle == NULL) {
319
fprintf (stderr, "_libthinkfinger_usb_read error: USB handle is NULL.\n");
324
usb_retval = usb_bulk_read (tf->usb_dev_handle, USB_RD_EP, bytes, size, USB_TIMEOUT);
325
if (usb_retval >= 0 && usb_retval != size)
326
fprintf (stderr, "Warning: usb_bulk_read expected to read 0x%x (read 0x%x bytes).\n",
329
usb_dump ("usb_bulk_read", (unsigned char*) bytes, size, usb_retval);
335
static void _libthinkfinger_usb_flush (libthinkfinger *tf)
339
_libthinkfinger_usb_read (tf, buf, DEFAULT_BULK_SIZE);
344
static struct usb_device *_libthinkfinger_usb_device_find (void)
346
struct usb_bus *usb_bus;
347
struct usb_device *dev = NULL;
353
/* TODO: Support systems with two fingerprint readers */
354
for (usb_bus = usb_busses; usb_bus; usb_bus = usb_bus->next) {
355
for (dev = usb_bus->devices; dev; dev = dev->next) {
356
if ((dev->descriptor.idVendor == USB_VENDOR_ID) &&
357
(dev->descriptor.idProduct == USB_PRODUCT_ID)) {
366
static void _libthinkfinger_usb_deinit_lock (libthinkfinger *tf)
368
if (pthread_mutex_lock (&tf->usb_deinit_mutex) < 0)
369
fprintf (stderr, "pthread_mutex_lock failed: (%s).\n", strerror (errno));
373
static void _libthinkfinger_usb_deinit_unlock (libthinkfinger *tf)
375
if (pthread_mutex_unlock (&tf->usb_deinit_mutex) < 0)
376
fprintf (stderr, "pthread_mutex_unlock failed: (%s).\n", strerror (errno));
380
static void _libthinkfinger_usb_deinit (libthinkfinger *tf)
384
_libthinkfinger_usb_deinit_lock (tf);
385
if (tf->usb_dev_handle == NULL) {
389
while (_libthinkfinger_task_running (tf) == true) {
390
termination_request = 0x00;
394
usb_retval = _libthinkfinger_usb_write (tf, deinit, sizeof(deinit));
395
if (usb_retval < 0 && usb_retval != -ETIMEDOUT)
397
_libthinkfinger_usb_flush (tf);
400
usb_release_interface (tf->usb_dev_handle, 0);
401
usb_close (tf->usb_dev_handle);
402
tf->usb_dev_handle = NULL;
404
_libthinkfinger_usb_deinit_unlock (tf);
408
static libthinkfinger_init_status _libthinkfinger_usb_init (libthinkfinger *tf)
410
libthinkfinger_init_status retval = TF_INIT_UNDEFINED;
411
struct usb_device *usb_dev;
413
usb_dev = _libthinkfinger_usb_device_find ();
414
if (usb_dev == NULL) {
416
fprintf (stderr, "USB error (device not found).\n");
418
retval = TF_INIT_USB_DEVICE_NOT_FOUND;
422
tf->usb_dev_handle = usb_open (usb_dev);
423
if (tf->usb_dev_handle == NULL) {
425
fprintf (stderr, "USB error (did not get handle).\n");
427
retval = TF_INIT_USB_OPEN_FAILED;
431
if (usb_claim_interface (tf->usb_dev_handle, 0) < 0) {
433
fprintf (stderr, "USB error (%s).\n", usb_strerror ());
435
retval = TF_INIT_USB_CLAIM_FAILED;
439
if (_libthinkfinger_usb_hello (tf->usb_dev_handle) < 0) {
441
fprintf (stderr, "USB error (sending hello failed).\n");
443
retval = TF_INIT_USB_HELLO_FAILED;
447
retval = TF_INIT_USB_INIT_SUCCESS;
452
static void _libthinkfinger_parse_scan_reply (libthinkfinger *tf, unsigned char *inbuf)
455
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
461
tf->state = TF_STATE_SWIPE_0;
465
switch (inbuf[18]-0x0c) {
467
tf->state = TF_STATE_SWIPE_1;
470
tf->state = TF_STATE_SWIPE_2;
477
tf->state = TF_STATE_SWIPE_SUCCESS;
480
tf->state = TF_STATE_ENROLL_SUCCESS;
486
tf->state = TF_STATE_SWIPE_FAILED;
489
#ifdef LIBTHINKFINGER_DEBUG
490
fprintf (stderr, "Unknown state 0x%x\n", inbuf[18]);
499
static int _libthinkfinger_store_fingerprint (libthinkfinger *tf, unsigned char *data)
506
if ((tf == NULL) || (tf->fd < 0)) {
507
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
511
if (write (tf->fd, data+18, 0x40-18) < 0) {
512
fprintf (stderr, "Error: %s.\n", strerror (errno));
516
len = ((data[5] & 0x0f) << 8) + data[6] - 0x37;
517
usb_retval = _libthinkfinger_usb_read (tf, inbuf, len);
518
if (usb_retval != len)
519
fprintf (stderr, "Warning: Expected 0x%x bytes but read 0x%x).\n", len, usb_retval);
520
if (write (tf->fd, inbuf, usb_retval) < 0)
521
fprintf (stderr, "Error: %s.\n", strerror (errno));
525
/* reset termination_request */
526
termination_request = 0x01;
531
/* returns 1 if it understood the packet */
532
static int _libthinkfinger_parse (libthinkfinger *tf, unsigned char *inbuf)
536
libthinkfinger_state state = tf->state;
537
const char fingerprint_is[] = {
538
0x00, 0x00, 0x00, 0x02, 0x12, 0xff, 0xff, 0xff,
543
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
547
_libthinkfinger_set_result_pending (tf, false);
551
tf->next_sequence = (inbuf[5] + 0x20) & 0x00ff;
552
if (tf->state == TF_STATE_ENROLL_SUCCESS && !memcmp(inbuf+9, fingerprint_is, 9)) {
553
retval = _libthinkfinger_store_fingerprint (tf, inbuf);
555
tf->state = TF_STATE_ACQUIRE_FAILED;
557
tf->state = TF_STATE_ACQUIRE_SUCCESS;
558
_libthinkfinger_task_stop (tf);
563
tf->state = TF_STATE_COMM_FAILED;
564
_libthinkfinger_task_stop (tf);
567
tf->state = TF_STATE_VERIFY_FAILED;
568
_libthinkfinger_task_stop (tf);
573
tf->state = TF_STATE_VERIFY_FAILED;
576
tf->state = TF_STATE_VERIFY_SUCCESS;
579
_libthinkfinger_task_stop (tf);
582
_libthinkfinger_parse_scan_reply (tf, inbuf);
591
/* device is busy, result pending */
592
_libthinkfinger_set_result_pending (tf, true);
598
if (tf->state != state && tf->cb != NULL)
599
tf->cb (tf->state, tf->cb_data);
607
static void _libthinkfinger_ask_scanner_raw (libthinkfinger *tf, int flags, char *ctrldata, int read_size, int write_size)
610
unsigned char inbuf[10240];
613
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
617
if (_libthinkfinger_task_running (tf) == false)
620
_libthinkfinger_set_result_pending (tf, true);
621
while (_libthinkfinger_result_pending (tf) == true) {
622
usb_retval = _libthinkfinger_usb_read (tf, (char *)&inbuf, read_size);
623
if (usb_retval < 0 && usb_retval != -ETIMEDOUT)
627
if (_libthinkfinger_parse (tf, inbuf))
629
if (_libthinkfinger_task_running (tf) == false)
631
if (_libthinkfinger_result_pending (tf) == true) {
632
_libthinkfinger_usb_write (tf, (char *)device_busy, sizeof(device_busy));
633
if (usb_retval < 0 && usb_retval != -ETIMEDOUT)
637
_libthinkfinger_set_result_pending (tf, false);
641
if (termination_request == 0x00) {
642
ctrldata[14] = termination_request;
643
tf->state = TF_STATE_SIGINT;
646
*((short *) (ctrldata+write_size-2)) = udf_crc ((u8*)&(ctrldata[4]), write_size-6, 0);
647
usb_retval = _libthinkfinger_usb_write (tf, (char *)ctrldata, write_size);
648
if (usb_retval < 0 && usb_retval != -ETIMEDOUT)
655
tf->state = TF_STATE_USB_ERROR;
659
case TF_STATE_ACQUIRE_SUCCESS:
660
case TF_STATE_ACQUIRE_FAILED:
661
case TF_STATE_VERIFY_SUCCESS:
662
case TF_STATE_VERIFY_FAILED:
663
case TF_STATE_SIGINT:
664
case TF_STATE_USB_ERROR:
665
case TF_STATE_COMM_FAILED: {
666
_libthinkfinger_task_stop (tf);
678
static libthinkfinger_init_status _libthinkfinger_init (libthinkfinger *tf)
680
libthinkfinger_init_status retval = TF_INIT_UNDEFINED;
683
retval = _libthinkfinger_usb_init (tf);
684
if (retval != TF_INIT_USB_INIT_SUCCESS)
687
_libthinkfinger_task_start (tf, TF_TASK_INIT);
689
_libthinkfinger_ask_scanner_raw (tf, SILENT, init[i].data, DEFAULT_BULK_SIZE, init[i].len);
690
} while (init[++i].data);
691
_libthinkfinger_usb_flush (tf);
692
_libthinkfinger_ask_scanner_raw (tf, SILENT, (char *)&init_end, 0x34, sizeof(init_end));
693
_libthinkfinger_task_stop (tf);
695
retval = TF_INIT_SUCCESS;
700
static void _libthinkfinger_scan (libthinkfinger *tf) {
701
tf->next_sequence = INITIAL_SEQUENCE;
702
_libthinkfinger_set_sigint (tf);
703
while (_libthinkfinger_task_running (tf)) {
704
scan_sequence[5] = tf->next_sequence;
705
_libthinkfinger_ask_scanner_raw (tf, PARSE, (char *)scan_sequence, DEFAULT_BULK_SIZE, sizeof (scan_sequence));
708
if (termination_request == 0x00) {
709
_libthinkfinger_usb_flush (tf);
714
_libthinkfinger_restore_sigint (tf);
718
static void _libthinkfinger_verify_run (libthinkfinger *tf)
723
tf->fd = open (tf->file, O_RDONLY | O_NOFOLLOW);
725
fprintf (stderr, "Error: %s.\n", strerror (errno));
726
_libthinkfinger_usb_flush (tf);
727
tf->state = TF_STATE_OPEN_FAILED;
731
filesize = read (tf->fd, ctrlbuf+header, sizeof(ctrlbuf)-header);
732
*((short *) (ctrlbuf+8)) = filesize + 28;
733
ctrlbuf[5] = (filesize+20511) >> 8;
734
ctrlbuf[6] = (filesize+20511) & 0xff;
735
ctrlbuf[header+filesize] = 0x4f;
736
ctrlbuf[header+filesize+1] = 0x47;
738
_libthinkfinger_task_start (tf, TF_TASK_VERIFY);
739
_libthinkfinger_ask_scanner_raw (tf, SILENT, ctrlbuf, DEFAULT_BULK_SIZE, header+filesize+2);
740
_libthinkfinger_scan (tf);
742
if (close (tf->fd) == 0)
748
libthinkfinger_result libthinkfinger_verify (libthinkfinger *tf)
750
libthinkfinger_result retval = TF_RESULT_UNDEFINED;
753
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
757
_libthinkfinger_init (tf);
758
_libthinkfinger_verify_run (tf);
759
retval = _libthinkfinger_get_result (tf->state);
764
static void _libthinkfinger_acquire_run (libthinkfinger *tf)
766
tf->fd = open (tf->file, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
768
fprintf (stderr, "Error: %s.\n", strerror (errno));
769
_libthinkfinger_usb_flush (tf);
770
tf->state = TF_STATE_OPEN_FAILED;
774
_libthinkfinger_task_start (tf, TF_TASK_ACQUIRE);
775
_libthinkfinger_ask_scanner_raw (tf, SILENT, enroll_init, DEFAULT_BULK_SIZE, sizeof(enroll_init));
776
_libthinkfinger_scan (tf);
778
if (close (tf->fd) == 0)
784
libthinkfinger_result libthinkfinger_acquire (libthinkfinger *tf)
786
libthinkfinger_result retval = TF_RESULT_UNDEFINED;
789
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
793
_libthinkfinger_init (tf);
794
_libthinkfinger_acquire_run (tf);
795
retval = _libthinkfinger_get_result (tf->state);
800
int libthinkfinger_set_file (libthinkfinger *tf, const char *file)
805
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
815
int libthinkfinger_set_callback (libthinkfinger *tf, libthinkfinger_state_cb cb, void *cb_data)
820
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
825
tf->cb_data = cb_data;
831
libthinkfinger *libthinkfinger_new (libthinkfinger_init_status *init_status)
833
libthinkfinger *tf = NULL;
835
tf = calloc(1, sizeof(libthinkfinger));
837
/* failed to allocate memory */
838
*init_status = TF_INIT_NO_MEMORY;
843
tf->usb_dev_handle = NULL;
846
tf->task = TF_TASK_UNDEFINED;
847
tf->task_running = false;
848
tf->state = TF_STATE_INITIAL;
851
if (pthread_mutex_init (&tf->usb_deinit_mutex, NULL) < 0)
852
fprintf (stderr, "pthread_mutex_init failed: (%s).\n", strerror (errno));
854
if ((*init_status = _libthinkfinger_init (tf)) != TF_INIT_SUCCESS)
857
_libthinkfinger_usb_flush (tf);
858
_libthinkfinger_usb_deinit (tf);
860
*init_status = TF_INIT_SUCCESS;
865
void libthinkfinger_free (libthinkfinger *tf)
868
fprintf (stderr, "Error: libthinkfinger not properly initialized.\n");
872
_libthinkfinger_usb_deinit (tf);
882
/** @mainpage libthinkfinger
884
* \section sec_intro Introduction
886
* ThinkFinger is a driver for the SGS Thomson Microelectronics fingerprint reader
887
* found in most IBM/Lenovo ThinkPads.