// SPDX-License-Identifier: GPL-2.0-only #include "gxfp/session.h" #include #include #include #include #include #include struct wait_context { struct gxfp_session *session; int result; }; static const char *error_string(int error) { return error < 0 ? strerror(-error) : "non-negative API error"; } static void sleep_ms(long milliseconds) { struct timespec delay = { .tv_sec = milliseconds / 1000, .tv_nsec = (milliseconds % 1000) * 1000000L, }; while (nanosleep(&delay, &delay) < 0 && errno == EINTR) ; } static void *wait_down_thread(void *opaque) { struct wait_context *context = opaque; context->result = gxfp_session_wait_finger_down(context->session, 30000); return NULL; } static int activate(struct gxfp_session *session) { int ret = gxfp_session_activate(session, 10000); if (ret) fprintf(stderr, "activate failed: %s (%d), state=%d\n", error_string(ret), ret, (int)gxfp_session_get_state(session)); return ret; } static int recover(struct gxfp_session *session) { int ret; ret = gxfp_session_deactivate(session); if (ret) { fprintf(stderr, "deactivate/reset failed: %s (%d)\n", error_string(ret), ret); return ret; } if (gxfp_session_get_state(session) != GXFP_SESSION_OPEN) { fprintf(stderr, "deactivate did not reach OPEN\n"); return -EPROTO; } return activate(session); } static int test_cancel_wait_down(struct gxfp_session *session) { struct wait_context context = { .session = session, .result = 0 }; pthread_t thread; int pthread_ret; int ret; puts("\n[1/3] cross-thread cancel during wait-finger-down"); ret = gxfp_session_arm_finger_down(session); if (ret) return ret; pthread_ret = pthread_create(&thread, NULL, wait_down_thread, &context); if (pthread_ret) return -pthread_ret; sleep_ms(500); gxfp_session_cancel(session); pthread_ret = pthread_join(thread, NULL); if (pthread_ret) return -pthread_ret; if (context.result != -ECANCELED) { fprintf(stderr, "expected -ECANCELED, got %d\n", context.result); return -EPROTO; } if (gxfp_session_get_state(session) != GXFP_SESSION_ERROR) { fprintf(stderr, "cancel did not leave session in ERROR\n"); return -EPROTO; } puts("PASS: waiter returned -ECANCELED; recovering hardware"); return recover(session); } static int capture_while_present(struct gxfp_session *session, struct gxfp_image12 *image) { int ret; ret = gxfp_session_arm_finger_down(session); if (ret) return ret; puts("Put finger on sensor and keep it there..."); ret = gxfp_session_wait_finger_down(session, 10000); if (ret) return ret; ret = gxfp_session_capture(session, image, 20000); if (!ret) printf("capture CRC: stored=0x%08x calculated=0x%08x\n", image->crc_stored, image->crc_calculated); return ret; } static int test_finger_up_timeout(struct gxfp_session *session) { struct gxfp_image12 image = { 0 }; int ret; puts("\n[2/3] wait-finger-up timeout and hardware recovery"); ret = capture_while_present(session, &image); if (ret) goto out; ret = gxfp_session_arm_finger_up(session); if (ret) goto out; puts("KEEP finger pressed for 3 seconds (timeout is intentional)..."); ret = gxfp_session_wait_finger_up(session, 3000); if (ret != -ETIMEDOUT) { fprintf(stderr, "expected -ETIMEDOUT, got %d\n", ret); ret = -EPROTO; goto out; } if (gxfp_session_get_state(session) != GXFP_SESSION_ERROR) { fprintf(stderr, "timeout did not leave session in ERROR\n"); ret = -EPROTO; goto out; } puts("PASS: finger-up timed out; remove finger now"); sleep_ms(1000); ret = recover(session); out: gxfp_image12_clear(&image); return ret; } static int test_normal_cycle(struct gxfp_session *session) { struct gxfp_image12 image = { 0 }; int ret; puts("\n[3/3] normal capture after both recoveries"); ret = capture_while_present(session, &image); if (ret) goto out; ret = gxfp_session_arm_finger_up(session); if (ret) goto out; puts("Remove finger..."); ret = gxfp_session_wait_finger_up(session, 10000); if (!ret && gxfp_session_get_state(session) != GXFP_SESSION_ACTIVE) ret = -EPROTO; if (!ret) puts("PASS: normal cycle completed in ACTIVE state"); out: gxfp_image12_clear(&image); return ret; } int main(int argc, char **argv) { const char *path = argc == 2 ? argv[1] : "/dev/gxfp"; struct gxfp_session *session = NULL; int ret; if (argc > 2) { fprintf(stderr, "usage: %s [device]\n", argv[0]); return EXIT_FAILURE; } ret = gxfp_session_open(&session, path); if (ret) goto out; ret = activate(session); if (ret) goto out; ret = test_cancel_wait_down(session); if (ret) goto out; ret = test_finger_up_timeout(session); if (ret) goto out; ret = test_normal_cycle(session); out: if (ret) fprintf(stderr, "regression failed: %s (%d), state=%d\n", error_string(ret), ret, session ? (int)gxfp_session_get_state(session) : -1); if (session) { int cleanup_ret = gxfp_session_deactivate(session); if (!ret && cleanup_ret) ret = cleanup_ret; gxfp_session_close(session); } if (!ret) puts("\nALL SESSION REGRESSIONS PASSED"); return ret ? EXIT_FAILURE : EXIT_SUCCESS; }