blob: 305a561c5446b4f80120162bfd3fc6a3699f9436 [file]
/* Copyright 2023 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "atomic.h"
#include "common.h"
#include "ec_commands.h"
#include "fpsensor/fpsensor.h"
#include "fpsensor/fpsensor_console.h"
#include "fpsensor/fpsensor_detect.h"
#include "fpsensor/fpsensor_modes.h"
#include "fpsensor/fpsensor_state.h"
#include "fpsensor/fpsensor_utils.h"
#include "system.h"
#include "util.h"
#include "watchdog.h"
#include <vector>
#ifdef CONFIG_ZEPHYR
#include <zephyr/shell/shell.h>
#endif
#ifdef CONFIG_CMD_FPSENSOR_DEBUG
/* --- Debug console commands --- */
/*
* Send the current Fingerprint buffer to the host
* it is formatted as an 8-bpp PGM ASCII file.
*
* In addition, it prepends a short Z-Modem download signature,
* which triggers automatically your preferred viewer if you configure it
* properly in "File transfer protocols" in the Minicom options menu.
* (as triggered by Ctrl-A O)
* +--------------------------------------------------------------------------+
* | Name Program Name U/D FullScr IO-Red. Multi |
* | A zmodem /usr/bin/sz -vv -b Y U N Y Y |
* [...]
* | L pgm /usr/bin/display_pgm N D N Y N |
* | M Zmodem download string activates... L |
*
* My /usr/bin/display_pgm looks like this:
* #!/bin/sh
* TMPF=$(mktemp)
* ascii-xfr -rdv ${TMPF}
* display ${TMPF}
*
* Alternative (if you're using screen as your terminal):
*
* From *outside* the chroot:
*
* Install ascii-xfr: sudo apt-get install minicom
* Install imagemagick: sudo apt-get install imagemagick
*
* Add the following to your ${HOME}/.screenrc:
*
* zmodem catch
* zmodem recvcmd '!!! bash -c "ascii-xfr -rdv /tmp/finger.pgm && display
* /tmp/finger.pgm"'
*
* From *outside the chroot*, use screen to connect to UART console:
*
* sudo screen -c ${HOME}/.screenrc /dev/pts/NN 115200
*
*/
test_export_static enum ec_error_list
upload_pgm_image(uint8_t *frame,
const struct fp_image_frame_params_v2 &image_frame_params)
{
uint8_t *ptr = frame;
uint8_t bytes_per_pixel = DIV_ROUND_UP(image_frame_params.bpp, 8);
if (bytes_per_pixel != 1 && bytes_per_pixel != 2) {
return EC_ERROR_UNKNOWN;
}
/* fake Z-modem ZRQINIT signature */
CPRINTF("#IGNORE for ZModem\r**\030B00");
crec_msleep(2000); /* let the download program start */
/* Print 8-bpp or 16-bpp PGM ASCII header */
CPRINTF("P2\n%d %d\n%d\n", image_frame_params.width,
image_frame_params.height,
(bytes_per_pixel == 2) ? 65535 : 255);
for (int y = 0; y < image_frame_params.height; y++) {
watchdog_reload();
for (int x = 0; x < image_frame_params.width;
x++, ptr += bytes_per_pixel) {
CPRINTF("%d ", (bytes_per_pixel == 2) ?
*(uint16_t *)ptr :
*ptr);
}
CPRINTF("\n");
cflush();
}
CPRINTF("\x04"); /* End Of Transmission */
return EC_SUCCESS;
}
static enum ec_error_list fp_console_action(uint32_t mode)
{
if (!(global_context.sensor_mode & FP_MODE_RESET_SENSOR))
CPRINTS("Waiting for finger ...");
uint32_t mode_output = 0;
const int rc = fp_set_sensor_mode(mode, &mode_output, std::nullopt);
if (rc != EC_RES_SUCCESS) {
/*
* EC host command errors do not directly map to console command
* errors.
*/
return EC_ERROR_UNKNOWN;
}
int tries = 200;
while (tries--) {
if (!(global_context.sensor_mode & FP_MODE_ANY_CAPTURE)) {
CPRINTS("done (events:%x)",
static_cast<int>(global_context.fp_events));
return EC_SUCCESS;
}
crec_usleep(100 * MSEC);
}
return EC_ERROR_TIMEOUT;
}
test_export_static int
get_image_frame_params(struct fp_image_frame_params_v2 &image_frame_params,
const enum fp_capture_type capture_type)
{
#if defined(HAVE_FP_PRIVATE_DRIVER) || defined(BOARD_HOST)
size_t fp_sensor_get_info_v2_size =
sizeof(struct ec_response_fp_info_v3) +
sizeof(struct fp_image_frame_params_v2) * FP_MAX_CAPTURE_TYPES;
std::vector<uint8_t> buffer(fp_sensor_get_info_v2_size);
auto *info = reinterpret_cast<ec_response_fp_info_v3 *>(buffer.data());
if (fp_sensor_get_info(info, buffer.size()) < 0) {
return EC_ERROR_UNKNOWN;
}
for (uint8_t i = 0; i < info->sensor_info.num_capture_types; ++i) {
if (info->image_frame_params[i].fp_capture_type ==
capture_type) {
image_frame_params = info->image_frame_params[i];
return EC_RES_SUCCESS;
}
}
return EC_ERROR_INVAL;
#else
return EC_ERROR_UNKNOWN;
#endif
}
static int command_fpcapture(int argc, const char **argv)
{
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
int capture_type = FP_CAPTURE_SIMPLE_IMAGE;
if (argc >= 2) {
char *e;
capture_type = strtoi(argv[1], &e, 0);
if (*e || capture_type < 0 ||
capture_type >= FP_CAPTURE_TYPE_MAX)
return EC_ERROR_PARAM1;
}
const uint32_t mode = FP_MODE_CAPTURE |
((capture_type << FP_MODE_CAPTURE_TYPE_SHIFT) &
FP_MODE_CAPTURE_TYPE_MASK);
const enum ec_error_list rc = fp_console_action(mode);
if (rc == EC_SUCCESS) {
struct fp_image_frame_params_v2 image_frame_params{};
int ret = get_image_frame_params(
image_frame_params,
global_context.current_capture_type);
if (ret != EC_RES_SUCCESS) {
CPRINTF("Failed to get image frame params, error: %d\n",
ret);
return ret;
}
return upload_pgm_image(fp_buffer + FP_SENSOR_IMAGE_OFFSET,
image_frame_params);
}
return rc;
}
DECLARE_CONSOLE_COMMAND(fpcapture, command_fpcapture, nullptr,
"Capture fingerprint in PGM format");
/* Transfer a chunk of the image from the host to the FPMCU
*
* Command format:
* fpupload <offset> <hex encoded pixel string>
*
* To limit the size of the commands, only a chunk of the image is sent for
* each command invocation.
*/
static int command_fpupload(int argc, const char **argv)
{
if (argc != 3)
return EC_ERROR_PARAM_COUNT;
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
int offset = atoi(argv[1]);
if (offset < 0)
return EC_ERROR_PARAM1;
uint8_t *dest = fp_buffer + FP_SENSOR_IMAGE_OFFSET + offset;
const char *pixels_str = argv[2];
while (*pixels_str) {
if (dest >= fp_buffer + FP_SENSOR_IMAGE_SIZE)
return EC_ERROR_PARAM1;
const char hex_str[] = { pixels_str[0], pixels_str[1], '\0' };
*dest = static_cast<uint8_t>(strtol(hex_str, nullptr, 16));
pixels_str += 2;
++dest;
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(fpupload, command_fpupload, nullptr,
"Copy fp image onto fpmcu fpsensor buffer");
/* Transfer an image from the FPMCU to the host
*
* Command format:
* fpdownload
*
* This is useful to verify the data was transferred correctly. Note that it
* requires the terminal to be configured as explained in the comment above
* upload_pgm_image().
*/
static int command_fpdownload(int argc, const char **argv)
{
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
struct fp_image_frame_params_v2 image_frame_params{};
int ret = get_image_frame_params(image_frame_params,
global_context.current_capture_type);
if (ret != EC_RES_SUCCESS) {
CPRINTF("Failed to get image frame params, error: %d\n", ret);
return ret;
}
return upload_pgm_image(fp_buffer + FP_SENSOR_IMAGE_OFFSET,
image_frame_params);
}
DECLARE_CONSOLE_COMMAND(fpdownload, command_fpdownload, nullptr,
"Copy fp image from fpmcu fpsensor buffer");
static int command_fpenroll(int argc, const char **argv)
{
enum ec_error_list rc;
int percent = 0;
static const char *const enroll_str[] = { "OK", "Low Quality",
"Immobile", "Low Coverage" };
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
while (1) {
int tries = 1000;
rc = fp_console_action(FP_MODE_ENROLL_SESSION |
FP_MODE_ENROLL_IMAGE);
if (rc != EC_SUCCESS)
break;
const uint32_t event = atomic_clear(&global_context.fp_events);
percent = EC_MKBP_FP_ENROLL_PROGRESS(event);
CPRINTS("Enroll capture: %s (%d%%)",
enroll_str[EC_MKBP_FP_ERRCODE(event) & 3], percent);
if (percent == 100) {
break;
}
/* wait for finger release between captures */
global_context.sensor_mode = FP_MODE_ENROLL_SESSION |
FP_MODE_FINGER_UP;
task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_UPDATE_CONFIG);
while (tries-- &&
global_context.sensor_mode & FP_MODE_FINGER_UP)
crec_usleep(20 * MSEC);
}
global_context.sensor_mode = 0; /* reset FP_MODE_ENROLL_SESSION */
task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_UPDATE_CONFIG);
return rc;
}
DECLARE_CONSOLE_COMMAND(fpenroll, command_fpenroll, nullptr,
"Enroll a new fingerprint");
static int command_fpinfo(int argc, const char **argv)
{
#if defined(HAVE_FP_PRIVATE_DRIVER) || defined(BOARD_HOST)
size_t fp_sensor_get_info_v2_size =
sizeof(struct ec_response_fp_info_v3) +
sizeof(struct fp_image_frame_params_v2) * FP_MAX_CAPTURE_TYPES;
std::vector<uint8_t> buffer(fp_sensor_get_info_v2_size);
auto *info = reinterpret_cast<ec_response_fp_info_v3 *>(buffer.data());
if (fp_sensor_get_info(info, buffer.size()) < 0) {
ccprintf("Failed to get fp_info_v2\n");
return EC_ERROR_UNKNOWN;
}
constexpr int align = 15;
ccprintf("%*s: 0x%X (%s)\n", align, "Vendor ID",
info->sensor_info.vendor_id,
fourcc_to_string(info->sensor_info.vendor_id).c_str());
ccprintf("%*s: 0x%X\n", align, "Product ID",
info->sensor_info.product_id);
ccprintf("%*s: 0x%X\n", align, "Model ID", info->sensor_info.model_id);
ccprintf("%*s: 0x%X\n", align, "Version", info->sensor_info.version);
ccprintf("%*s: 0x%X\n", align, "Error State", info->sensor_info.errors);
ccprintf("%*s: %s\n", align, "Sensor Strap",
fp_sensor_type_to_str(fpsensor_detect_get_type()));
for (uint16_t i = 0; i < info->sensor_info.num_capture_types; ++i) {
ccprintf("FP Capture Type: %d\n",
info->image_frame_params[i].fp_capture_type);
ccprintf(" %*s: %u x %u %ubpp\n", align, "Sensor (w x h)",
info->image_frame_params[i].width,
info->image_frame_params[i].height,
info->image_frame_params[i].bpp);
ccprintf(" %*s: %u\n", align, "Frame Size",
info->image_frame_params[i].frame_size);
ccprintf(" %*s: %u\n", align, "Image Data Offset (bytes)",
info->image_frame_params[i].image_data_offset_bytes);
ccprintf(" %*s: 0x%X (%s)\n", align, "Pixel Format",
info->image_frame_params[i].pixel_format,
fourcc_to_string(
info->image_frame_params[i].pixel_format)
.c_str());
}
return EC_SUCCESS;
#else
ccprintf("fpinfo command not supported on this firmware.\n");
return EC_ERROR_UNKNOWN;
#endif
}
DECLARE_SAFE_CONSOLE_COMMAND(fpinfo, command_fpinfo, nullptr,
"Print fingerprint system info");
static int command_fpmatch(int argc, const char **argv)
{
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
const enum ec_error_list rc = fp_console_action(FP_MODE_MATCH);
const uint32_t event = atomic_clear(&global_context.fp_events);
if (rc == EC_SUCCESS && event & EC_MKBP_FP_MATCH) {
const uint32_t match_errcode = EC_MKBP_FP_ERRCODE(event);
CPRINTS("Match: %s (%d)",
fp_match_success(match_errcode) ? "YES" : "NO",
match_errcode);
}
return rc;
}
DECLARE_CONSOLE_COMMAND(fpmatch, command_fpmatch, nullptr,
"Run match algorithm against finger");
static int command_fpclear(int argc, const char **argv)
{
/*
* We intentionally run this on the fp_task so that we use the
* same code path as host commands.
*/
const enum ec_error_list rc = fp_console_action(FP_MODE_RESET_SENSOR);
if (rc != EC_SUCCESS)
CPRINTS("Failed to clear fingerprint context: %d", rc);
atomic_clear(&global_context.fp_events);
return rc;
}
DECLARE_CONSOLE_COMMAND(fpclear, command_fpclear, nullptr,
"Clear fingerprint sensor context");
static int command_fpmaintenance(int argc, const char **argv)
{
#ifdef HAVE_FP_PRIVATE_DRIVER
uint32_t mode_output = 0;
const int rc = fp_set_sensor_mode(FP_MODE_SENSOR_MAINTENANCE,
&mode_output, std::nullopt);
if (rc != EC_RES_SUCCESS) {
/*
* EC host command errors do not directly map to console command
* errors.
*/
return EC_ERROR_UNKNOWN;
}
/* Block console until maintenance is finished. */
while (global_context.sensor_mode & FP_MODE_SENSOR_MAINTENANCE) {
crec_usleep(100 * MSEC);
}
#endif /* #ifdef HAVE_FP_PRIVATE_DRIVER */
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(fpmaintenance, command_fpmaintenance, nullptr,
"Run fingerprint sensor maintenance");
#endif /* CONFIG_CMD_FPSENSOR_DEBUG */