| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "biod/cros_fp_device.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| |
| #include <algorithm> |
| |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <chromeos/ec/cros_ec_dev.h> |
| |
| #include "biod/ec_command.h" |
| #include "biod/ec_command_async.h" |
| #include "biod/fp_context_command_factory.h" |
| |
| namespace { |
| |
| // Upper bound of the host command packet transfer size. |
| constexpr int kMaxPacketSize = 544; |
| |
| std::string FourCC(const uint32_t a) { |
| return base::StringPrintf( |
| "%c%c%c%c", static_cast<char>(a), static_cast<char>(a >> 8), |
| static_cast<char>(a >> 16), static_cast<char>(a >> 24)); |
| } |
| |
| } // namespace |
| |
| namespace biod { |
| |
| constexpr char CrosFpDevice::kCrosFpPath[]; |
| |
| CrosFpDevice::~CrosFpDevice() { |
| // Current session is gone, clean-up temporary state in the FP MCU. |
| if (cros_fd_.is_valid()) |
| ResetContext(); |
| } |
| |
| bool CrosFpDevice::EcProtoInfo(ssize_t* max_read, ssize_t* max_write) { |
| /* read max request / response size from the MCU for protocol v3+ */ |
| EcCommand<EmptyParam, struct ec_response_get_protocol_info> cmd( |
| EC_CMD_GET_PROTOCOL_INFO); |
| // We retry this command because it is known to occasionally fail |
| // with ETIMEDOUT on first attempt. |
| if (!cmd.RunWithMultipleAttempts(cros_fd_.get(), kMaxIoAttempts)) |
| return false; |
| |
| *max_read = |
| cmd.Resp()->max_response_packet_size - sizeof(struct ec_host_response); |
| // TODO(vpalatin): workaround for b/78544921, can be removed if MCU is fixed. |
| *max_write = |
| cmd.Resp()->max_request_packet_size - sizeof(struct ec_host_request) - 4; |
| return true; |
| } |
| |
| ssize_t CrosFpDevice::ReadVersion(char* buffer, size_t size) { |
| ssize_t ret; |
| for (int retry = 0; retry < kMaxIoAttempts; retry++) { |
| ret = read(cros_fd_.get(), buffer, size); |
| if (ret >= 0) { |
| LOG_IF(INFO, retry > 0) |
| << "FPMCU read cros_fp device succeeded on attempt " << retry + 1 |
| << "/" << kMaxIoAttempts << "."; |
| return ret; |
| } |
| if (errno != ETIMEDOUT) { |
| PLOG(ERROR) << "FPMCU failed to read cros_fp device on attempt " |
| << retry + 1 << "/" << kMaxIoAttempts |
| << ", retry is not allowed for error"; |
| return ret; |
| } |
| PLOG(ERROR) << "FPMCU failed to read cros_fp device on attempt " |
| << retry + 1 << "/" << kMaxIoAttempts; |
| } |
| |
| return ret; |
| } |
| |
| bool CrosFpDevice::EcDevInit() { |
| // This is a special read (before events are enabled) that can fail due |
| // to ETIMEDOUT. This is because the first read with events disabled |
| // triggers a get_version request to the FPMCU, which can timeout. |
| // TODO(b/131438292): Remove the hardcoded size for the version buffer. |
| char version[80]; |
| ssize_t ret = ReadVersion(version, sizeof(version) - 1); |
| if (ret <= 0) { |
| LOG(ERROR) << "Failed to read cros_fp device version, read returned " << ret |
| << "."; |
| return false; |
| } |
| version[ret] = '\0'; |
| LOG(INFO) << "cros_fp device version: " << version; |
| char* s = strchr(version, '\n'); |
| if (s) |
| *s = '\0'; |
| if (strcmp(version, CROS_EC_DEV_VERSION)) { |
| LOG(ERROR) << "Invalid device version"; |
| return false; |
| } |
| |
| if (!EcProtoInfo(&max_read_size_, &max_write_size_)) { |
| LOG(ERROR) << "Failed to get cros_fp protocol info."; |
| return false; |
| } |
| |
| unsigned long mask = 1 << EC_MKBP_EVENT_FINGERPRINT; // NOLINT(runtime/int) |
| if (ioctl(cros_fd_.get(), CROS_EC_DEV_IOCEVENTMASK_V2, mask) < 0) { |
| LOG(ERROR) << "Fail to request fingerprint events"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void CrosFpDevice::OnEventReadable() { |
| struct ec_response_get_next_event evt; |
| ssize_t sz = read(cros_fd_.get(), &evt, sizeof(evt)); |
| |
| // We are interested only in fingerprint events, discard the other ones. |
| if (evt.event_type != EC_MKBP_EVENT_FINGERPRINT || |
| sz < sizeof(evt.event_type) + sizeof(evt.data.fp_events)) |
| return; |
| |
| // Properly aligned event value. |
| uint32_t events; |
| memcpy(&events, &evt.data.fp_events, sizeof(events)); |
| mkbp_event_.Run(events); |
| } |
| |
| bool CrosFpDevice::SetFpMode(const FpMode& mode) { |
| EcCommand<struct ec_params_fp_mode, struct ec_response_fp_mode> cmd( |
| EC_CMD_FP_MODE, kVersionZero, {.mode = mode.RawVal()}); |
| bool ret = cmd.Run(cros_fd_.get()); |
| if (ret) { |
| return true; |
| } |
| |
| // In some cases the EC Command might go through, but the AP suspends |
| // before the EC can ACK it. When the AP wakes up, it considers the |
| // EC command to have timed out. Since this seems to happen during mode |
| // setting, check the mode in case of a failure. |
| FpMode cur_mode; |
| if (!GetFpMode(&cur_mode)) { |
| LOG(ERROR) << "Failed to get FP mode to verify mode was set in the MCU."; |
| return false; |
| } |
| if (cur_mode == mode) { |
| LOG(WARNING) |
| << "EC Command to set mode failed, but mode was set successfully."; |
| return true; |
| } else { |
| LOG(ERROR) << "EC command to set FP mode: " << mode |
| << " failed; current FP mode: " << cur_mode; |
| } |
| return false; |
| } |
| |
| bool CrosFpDevice::GetFpMode(FpMode* mode) { |
| EcCommand<struct ec_params_fp_mode, struct ec_response_fp_mode> cmd( |
| EC_CMD_FP_MODE, kVersionZero, |
| {.mode = static_cast<uint32_t>(FP_MODE_DONT_CHANGE)}); |
| if (!cmd.Run(cros_fd_.get())) { |
| LOG(ERROR) << "Failed to get FP mode from MCU."; |
| return false; |
| } |
| |
| *mode = FpMode(cmd.Resp()->mode); |
| return true; |
| } |
| |
| bool CrosFpDevice::FpFrame(int index, std::vector<uint8_t>* frame) { |
| EcCommand<struct ec_params_fp_frame, uint8_t[kMaxPacketSize]> cmd( |
| EC_CMD_FP_FRAME); |
| |
| uint32_t offset = index << FP_FRAME_INDEX_SHIFT; |
| uint8_t* payload = cmd.Resp()[0]; |
| auto pos = frame->begin(); |
| while (pos < frame->end()) { |
| uint32_t len = std::min(max_read_size_, frame->end() - pos); |
| cmd.SetReq({.offset = offset, .size = len}); |
| cmd.SetRespSize(len); |
| int retries = 0; |
| const int max_retries = 50, delay_ms = 100; |
| while (!cmd.Run(cros_fd_.get())) { |
| if (!(offset & FP_FRAME_OFFSET_MASK)) { |
| // On the first request, the EC might still be rate-limiting. Retry in |
| // that case. |
| if (cmd.Result() == EC_RES_BUSY && retries < max_retries) { |
| retries++; |
| LOG(INFO) << "Retrying FP_FRAME, attempt " << retries; |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(delay_ms)); |
| continue; |
| } |
| } |
| LOG(ERROR) << "FP_FRAME command failed @ 0x" << std::hex << offset; |
| return false; |
| } |
| std::copy(payload, payload + len, pos); |
| offset += len; |
| pos += len; |
| } |
| return true; |
| } |
| |
| EcCmdVersionSupportStatus CrosFpDevice::EcCmdVersionSupported(uint16_t cmd_code, |
| uint32_t ver) { |
| EcCommand<struct ec_params_get_cmd_versions_v1, |
| struct ec_response_get_cmd_versions> |
| cmd(EC_CMD_GET_CMD_VERSIONS, 1, {.cmd = cmd_code}); |
| |
| if (!cmd.RunWithMultipleAttempts(cros_fd_.get(), kMaxIoAttempts) && |
| cmd.Result() == kEcCommandUninitializedResult) |
| // Running EC_CMD_GET_CMD_VERSIONS itself failed (e.g. due to timeout). |
| return EcCmdVersionSupportStatus::UNKNOWN; |
| |
| if (cmd.Result() != EC_RES_SUCCESS) |
| // Command not found on EC. |
| return EcCmdVersionSupportStatus::UNSUPPORTED; |
| |
| if ((cmd.Resp()->version_mask & EC_VER_MASK(ver)) == 0) |
| // Command found but version not supported. |
| return EcCmdVersionSupportStatus::UNSUPPORTED; |
| |
| return EcCmdVersionSupportStatus::SUPPORTED; |
| } |
| |
| bool CrosFpDevice::SupportsPositiveMatchSecret() { |
| switch (EcCmdVersionSupported(EC_CMD_FP_READ_MATCH_SECRET, 0)) { |
| case EcCmdVersionSupportStatus::SUPPORTED: |
| LOG(INFO) << "Positive match secret is supported."; |
| return true; |
| case EcCmdVersionSupportStatus::UNSUPPORTED: |
| LOG(INFO) << "Positive match secret is not supported."; |
| return false; |
| case EcCmdVersionSupportStatus::UNKNOWN: |
| LOG(WARNING) << "Failed to check support for positive match secret. " |
| "Defaulting to not supporting."; |
| return false; |
| } |
| } |
| |
| bool CrosFpDevice::FpReadMatchSecret(uint16_t index, |
| brillo::SecureBlob* secret) { |
| EcCommand<struct ec_params_fp_read_match_secret, |
| struct ec_response_fp_read_match_secret> |
| cmd(EC_CMD_FP_READ_MATCH_SECRET, 0, {.fgr = index}); |
| |
| if (!cmd.Run(cros_fd_.get()) && |
| cmd.Result() == kEcCommandUninitializedResult) { |
| LOG(ERROR) << "Failed to run EC_CMD_FP_READ_MATCH_SECRET command."; |
| return false; |
| } |
| if (cmd.Result() != EC_RES_SUCCESS) { |
| LOG(ERROR) << "Failed to read positive match secret, result: " |
| << cmd.Result() << "."; |
| return false; |
| } |
| secret->resize(sizeof(cmd.Resp()->positive_match_secret)); |
| std::copy(cmd.Resp()->positive_match_secret, |
| cmd.Resp()->positive_match_secret + |
| sizeof(cmd.Resp()->positive_match_secret), |
| secret->begin()); |
| brillo::SecureMemset(cmd.Resp()->positive_match_secret, 0, |
| sizeof(cmd.Resp()->positive_match_secret)); |
| return true; |
| } |
| |
| bool CrosFpDevice::UpdateFpInfo() { |
| info_ = ec_command_factory_->FpInfoCommand(); |
| |
| if (!info_->Run(cros_fd_.get())) { |
| LOG(ERROR) << "Failed to get FP information."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CrosFpDevice::GetFpStats(int* capture_ms, |
| int* matcher_ms, |
| int* overall_ms) { |
| EcCommand<EmptyParam, struct ec_response_fp_stats> cmd(EC_CMD_FP_STATS); |
| if (!cmd.Run(cros_fd_.get())) |
| return false; |
| |
| uint8_t inval = cmd.Resp()->timestamps_invalid; |
| if (inval & (FPSTATS_CAPTURE_INV | FPSTATS_MATCHING_INV)) |
| return false; |
| |
| *capture_ms = cmd.Resp()->capture_time_us / 1000; |
| *matcher_ms = cmd.Resp()->matching_time_us / 1000; |
| *overall_ms = cmd.Resp()->overall_time_us / 1000; |
| |
| return true; |
| } |
| |
| // static |
| bool CrosFpDevice::WaitOnEcBoot(const base::ScopedFD& cros_fp_fd, |
| ec_current_image expected_image) { |
| int tries = 50; |
| ec_current_image image = EC_IMAGE_UNKNOWN; |
| |
| while (tries) { |
| tries--; |
| // Check the EC has the right image. |
| EcCommand<EmptyParam, struct ec_response_get_version> cmd( |
| EC_CMD_GET_VERSION); |
| if (!cmd.Run(cros_fp_fd.get())) { |
| LOG(ERROR) << "Failed to retrieve cros_fp firmware version."; |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500)); |
| continue; |
| } |
| image = static_cast<ec_current_image>(cmd.Resp()->current_image); |
| if (image == expected_image) { |
| LOG(INFO) << "EC image is " << (image == EC_IMAGE_RO ? "RO" : "RW") |
| << "."; |
| return true; |
| } |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); |
| } |
| LOG(ERROR) << "EC rebooted to incorrect image " << image; |
| return false; |
| } |
| |
| // static |
| bool CrosFpDevice::GetVersion(const base::ScopedFD& cros_fp_fd, |
| EcVersion* ver) { |
| CHECK(ver); |
| |
| EcCommand<EmptyParam, struct ec_response_get_version> cmd(EC_CMD_GET_VERSION); |
| if (!cmd.Run(cros_fp_fd.get())) { |
| LOG(ERROR) << "Failed to fetch cros_fp firmware version."; |
| return false; |
| } |
| |
| // buffers should already be null terminated -- this is a safeguard |
| cmd.Resp()->version_string_ro[sizeof(cmd.Resp()->version_string_ro) - 1] = |
| '\0'; |
| cmd.Resp()->version_string_rw[sizeof(cmd.Resp()->version_string_rw) - 1] = |
| '\0'; |
| |
| ver->ro_version = std::string(cmd.Resp()->version_string_ro); |
| ver->rw_version = std::string(cmd.Resp()->version_string_rw); |
| ver->current_image = static_cast<ec_current_image>(cmd.Resp()->current_image); |
| return true; |
| } |
| |
| bool CrosFpDevice::EcReboot(ec_current_image to_image) { |
| DCHECK(to_image == EC_IMAGE_RO || to_image == EC_IMAGE_RW); |
| |
| EcCommand<EmptyParam, EmptyParam> cmd_reboot(EC_CMD_REBOOT); |
| // Don't expect a return code, cros_fp has rebooted. |
| cmd_reboot.Run(cros_fd_.get()); |
| |
| if (!WaitOnEcBoot(cros_fd_, EC_IMAGE_RO)) { |
| LOG(ERROR) << "EC did not come back up after reboot."; |
| return false; |
| } |
| |
| if (to_image == EC_IMAGE_RO) { |
| // Tell the EC to remain in RO. |
| EcCommand<struct ec_params_rwsig_action, EmptyParam> cmd_rwsig( |
| EC_CMD_RWSIG_ACTION); |
| cmd_rwsig.SetReq({.action = RWSIG_ACTION_ABORT}); |
| if (!cmd_rwsig.Run(cros_fd_.get())) { |
| LOG(ERROR) << "Failed to keep cros_fp in RO."; |
| return false; |
| } |
| } |
| |
| // EC jumps to RW after 1 second. Wait enough time in case we want to reboot |
| // to RW. In case we wanted to remain in RO, wait anyway to ensure that the EC |
| // received the instructions. |
| base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(3)); |
| |
| if (!WaitOnEcBoot(cros_fd_, to_image)) { |
| LOG(ERROR) << "EC did not load the right image."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CrosFpDevice::AddEntropy(bool reset) { |
| // Create the secret. |
| EcCommandAsync<struct ec_params_rollback_add_entropy, EmptyParam> |
| cmd_add_entropy(EC_CMD_ADD_ENTROPY, ADD_ENTROPY_GET_RESULT, |
| {.poll_for_result_num_attempts = 20, |
| .poll_interval = base::TimeDelta::FromMilliseconds(100), |
| // The EC temporarily stops responding to EC commands |
| // when this command is run, so we will keep trying until |
| // we get success (or time out). |
| .validate_poll_result = false}); |
| if (reset) { |
| cmd_add_entropy.SetReq({.action = ADD_ENTROPY_RESET_ASYNC}); |
| } else { |
| cmd_add_entropy.SetReq({.action = ADD_ENTROPY_ASYNC}); |
| } |
| |
| if (cmd_add_entropy.Run(cros_fd_.get())) { |
| LOG(INFO) << "Entropy has been successfully added."; |
| return true; |
| } |
| LOG(ERROR) << "Failed to check status of entropy command."; |
| return false; |
| } |
| |
| bool CrosFpDevice::GetRollBackInfoId(int32_t* block_id) { |
| EcCommand<EmptyParam, struct ec_response_rollback_info> cmd_rb_info( |
| EC_CMD_ROLLBACK_INFO); |
| if (!cmd_rb_info.Run(cros_fd_.get())) { |
| return false; |
| } |
| |
| *block_id = cmd_rb_info.Resp()->id; |
| return true; |
| } |
| |
| bool CrosFpDevice::InitEntropy(bool reset) { |
| int32_t block_id; |
| if (!GetRollBackInfoId(&block_id)) { |
| LOG(ERROR) << "Failed to read block ID from FPMCU."; |
| return false; |
| } |
| |
| if (!reset && block_id != 0) { |
| // Secret has been set. |
| LOG(INFO) << "Entropy source had been initialized previously."; |
| return true; |
| } |
| LOG(INFO) << "Entropy source has not been initialized yet."; |
| |
| bool success = UpdateEntropy(reset); |
| if (!success) { |
| LOG(INFO) << "Entropy addition failed."; |
| return false; |
| } |
| LOG(INFO) << "Entropy has been successfully added."; |
| return true; |
| } |
| |
| bool CrosFpDevice::Init() { |
| cros_fd_ = base::ScopedFD(open(kCrosFpPath, O_RDWR)); |
| if (cros_fd_.get() < 0) { |
| LOG(ERROR) << "Failed to open " << kCrosFpPath; |
| return false; |
| } |
| |
| if (!EcDevInit()) |
| return false; |
| |
| if (!InitEntropy(false)) { |
| return false; |
| } |
| |
| // Clean MCU memory if anything is remaining from aborted sessions |
| ResetContext(); |
| |
| // Retrieve the sensor information / parameters. |
| if (!UpdateFpInfo()) |
| return false; |
| |
| LOG(INFO) << "CROS FP Sensor Info "; |
| LOG(INFO) << " Vendor ID : " << FourCC(info_->sensor_id()->vendor_id); |
| LOG(INFO) << " Product ID : " << info_->sensor_id()->product_id; |
| LOG(INFO) << " Model ID : 0x" << std::hex << info_->sensor_id()->model_id; |
| LOG(INFO) << " Version : " << info_->sensor_id()->version; |
| std::string error_flags; |
| if ((info_->GetFpSensorErrors() & FpSensorErrors::kNoIrq) != |
| FpSensorErrors::kNone) |
| error_flags += "NO_IRQ "; |
| if ((info_->GetFpSensorErrors() & FpSensorErrors::kSpiCommunication) != |
| FpSensorErrors::kNone) |
| error_flags += "SPI_COMM "; |
| if ((info_->GetFpSensorErrors() & FpSensorErrors::kBadHardwareID) != |
| FpSensorErrors::kNone) |
| error_flags += "BAD_HWID "; |
| if ((info_->GetFpSensorErrors() & FpSensorErrors::kInitializationFailure) != |
| FpSensorErrors::kNone) |
| error_flags += "INIT_FAIL"; |
| LOG(INFO) << " Errors : " << error_flags; |
| LOG(INFO) << "CROS FP Image Info "; |
| // Prints the pixel format in FOURCC format. |
| LOG(INFO) << " Pixel Format : " |
| << FourCC(info_->sensor_image()->pixel_format); |
| LOG(INFO) << " Image Data Size : " << info_->sensor_image()->frame_size; |
| LOG(INFO) << " Image Dimensions : " << info_->sensor_image()->width << "x" |
| << info_->sensor_image()->height << " " |
| << info_->sensor_image()->bpp << " bpp"; |
| LOG(INFO) << "CROS FP Finger Template Info "; |
| LOG(INFO) << " Template data format : " << info_->template_info()->version; |
| LOG(INFO) << " Template Data Size : " << info_->template_info()->size; |
| LOG(INFO) << " Max number of fingers : " |
| << info_->template_info()->max_templates; |
| |
| watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| cros_fd_.get(), base::BindRepeating(&CrosFpDevice::OnEventReadable, |
| base::Unretained(this))); |
| if (!watcher_) { |
| LOG(ERROR) << "Unable to watch MKBP events"; |
| return false; |
| } |
| |
| if (!input_device_.Init()) { |
| LOG(ERROR) << "Failed to create Uinput device"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CrosFpDevice::GetDirtyMap(std::bitset<32>* bitmap) { |
| // Retrieve the up-to-date dirty bitmap from the MCU. |
| if (!UpdateFpInfo()) |
| return false; |
| |
| *bitmap = info_->template_info()->dirty; |
| return true; |
| } |
| |
| bool CrosFpDevice::GetIndexOfLastTemplate(int* index) { |
| if (!UpdateFpInfo()) |
| return false; |
| *index = info_->template_info()->num_valid - 1; |
| if (*index < 0 || *index >= MaxTemplateCount()) { |
| LOG(ERROR) << "Invalid index of last template: " << *index << "."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool CrosFpDevice::GetPositiveMatchSecret(int index, |
| brillo::SecureBlob* secret) { |
| if (index == kLastTemplate) { |
| if (!GetIndexOfLastTemplate(&index)) |
| return false; |
| } |
| return FpReadMatchSecret(static_cast<uint16_t>(index), secret); |
| } |
| |
| bool CrosFpDevice::GetTemplate(int index, VendorTemplate* out) { |
| if (index == kLastTemplate) { |
| if (!GetIndexOfLastTemplate(&index)) |
| return false; |
| // Is the last one really a new created one ? |
| const auto& dirty = info_->template_info()->dirty; |
| if (index >= dirty.size() || !dirty.test(index)) |
| return false; |
| } |
| out->resize(static_cast<size_t>(info_->template_info()->size)); |
| // In the EC_CMD_FP_FRAME host command, the templates are indexed starting |
| // from 1 (aka FP_FRAME_INDEX_TEMPLATE), as 0 (aka FP_FRAME_INDEX_RAW_IMAGE) |
| // is used for the finger image. |
| return FpFrame(index + FP_FRAME_INDEX_TEMPLATE, out); |
| } |
| |
| bool CrosFpDevice::UploadTemplate(const VendorTemplate& tmpl) { |
| union cmd_with_data { |
| struct ec_params_fp_template req; |
| uint8_t _fullsize[kMaxPacketSize]; |
| }; |
| EcCommand<union cmd_with_data, EmptyParam> cmd(EC_CMD_FP_TEMPLATE); |
| struct ec_params_fp_template* req = &cmd.Req()->req; |
| |
| size_t max_chunk = |
| max_write_size_ - offsetof(struct ec_params_fp_template, data); |
| |
| auto pos = tmpl.begin(); |
| while (pos < tmpl.end()) { |
| size_t remaining = tmpl.end() - pos; |
| uint32_t tlen = std::min(max_chunk, remaining); |
| req->offset = pos - tmpl.begin(); |
| req->size = tlen | (remaining == tlen ? FP_TEMPLATE_COMMIT : 0); |
| std::copy(pos, pos + tlen, req->data); |
| cmd.SetReqSize(tlen + sizeof(struct ec_params_fp_template)); |
| if (!cmd.Run(cros_fd_.get()) || cmd.Result() != EC_RES_SUCCESS) { |
| LOG(ERROR) << "FP_TEMPLATE command failed @ " << pos - tmpl.begin(); |
| return false; |
| } |
| pos += tlen; |
| } |
| return true; |
| } |
| |
| bool CrosFpDevice::SetContext(std::string user_hex) { |
| auto fp_context_cmd = ec_command_factory_->FpContextCommand(this, user_hex); |
| |
| if (!fp_context_cmd) { |
| LOG(ERROR) << "Unable to create FP context command"; |
| biod_metrics_->SendSetContextSuccess(false); |
| return false; |
| } |
| |
| bool success = true; |
| FpMode original_mode; |
| if (!GetFpMode(&original_mode)) { |
| LOG(ERROR) << "Unable to get FP Mode."; |
| success = false; |
| } |
| |
| // FPMCU does not allow resetting context when mode is not none, to prevent |
| // interrupting sensor library and leaking memory. However, for removing |
| // fingerprints, since the user is in the fingerprint list UI, FPMCU is in |
| // match mode. In this case we have to exit match mode and re-enter after |
| // setting context. |
| if (original_mode == FpMode(FpMode::Mode::kMatch)) { |
| LOG(INFO) << "Attempting to set context with match mode."; |
| if (!SetFpMode(FpMode(FpMode::Mode::kNone))) { |
| LOG(ERROR) << "Setting FPMCU context: failed to switch mode from match " |
| << "to none."; |
| success = false; |
| } |
| } else if (original_mode != FpMode(FpMode::Mode::kNone)) { |
| LOG(ERROR) << "Attempting to set context with mode: " << original_mode |
| << "."; |
| success = false; |
| } |
| biod_metrics_->SendSetContextMode(original_mode); |
| |
| success &= fp_context_cmd->Run(cros_fd_.get()); |
| |
| if (original_mode == FpMode(FpMode::Mode::kMatch)) { |
| if (!SetFpMode(original_mode)) { |
| LOG(ERROR) << "Setting FPMCU context: failed to switch back to match " |
| << "mode after setting context."; |
| success = false; |
| } |
| } |
| |
| biod_metrics_->SendSetContextSuccess(success); |
| return success; |
| } |
| |
| bool CrosFpDevice::ResetContext() { |
| FpMode cur_mode; |
| if (!GetFpMode(&cur_mode)) { |
| LOG(ERROR) << "Unable to get FP Mode."; |
| } |
| |
| // ResetContext is called when we no longer expect any session to be running |
| // (such as when the user logs out or biod is starting/stopping). This check |
| // exists to make sure that we have disabled any matching in the firmware |
| // when this is called. See https://crbug.com/980614 for details. |
| if (cur_mode != FpMode(FpMode::Mode::kNone)) { |
| LOG(ERROR) << "Attempting to reset context with mode: " << cur_mode; |
| } |
| |
| CHECK(biod_metrics_); |
| biod_metrics_->SendResetContextMode(cur_mode); |
| |
| return SetContext(std::string()); |
| } |
| |
| bool CrosFpDevice::UpdateEntropy(bool reset) { |
| // Stash the most recent block id. |
| int32_t block_id; |
| if (!GetRollBackInfoId(&block_id)) { |
| LOG(ERROR) << "Failed to block ID from FPMCU before entropy reset."; |
| return false; |
| } |
| |
| // Reboot the EC to RO. |
| if (!EcReboot(EC_IMAGE_RO)) { |
| LOG(ERROR) << "Failed to reboot cros_fp to initialise entropy."; |
| return false; |
| } |
| |
| // Initialize the secret. |
| if (!AddEntropy(reset)) { |
| LOG(ERROR) << "Failed to add entropy."; |
| return false; |
| } |
| |
| // Entropy added, reboot cros_fp to RW. |
| if (!EcReboot(EC_IMAGE_RW)) { |
| LOG(ERROR) << "Failed to reboot cros_fp after initializing entropy."; |
| return false; |
| } |
| |
| int32_t new_block_id; |
| if (!GetRollBackInfoId(&new_block_id)) { |
| LOG(ERROR) << "Failed to block ID from FPMCU after entropy reset."; |
| return false; |
| } |
| |
| int32_t block_id_diff = 2; |
| if (!reset) { |
| block_id_diff = 1; |
| } |
| |
| if (new_block_id != block_id + block_id_diff) { |
| LOG(ERROR) << "Entropy source has not been updated; old block_id: " |
| << block_id << ", new block_id: " << new_block_id; |
| return false; |
| } |
| return true; |
| } |
| |
| int CrosFpDevice::MaxTemplateCount() { |
| if (!info_ || !info_->template_info()) { |
| UpdateFpInfo(); |
| } |
| CHECK(info_); |
| CHECK(info_->template_info()); |
| return info_->template_info()->max_templates; |
| } |
| |
| int CrosFpDevice::TemplateVersion() { |
| if (!info_ || !info_->template_info()) { |
| UpdateFpInfo(); |
| } |
| CHECK(info_); |
| CHECK(info_->template_info()); |
| return info_->template_info()->version; |
| } |
| |
| } // namespace biod |