blob: dadcc54210edc24110cd5d283b11c3f1de63052d [file] [log] [blame]
/*
* Copyright 2021 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 "common/sensor_hal_client_impl.h"
#include <iterator>
#include <utility>
#include <base/bind.h>
#include <base/containers/contains.h>
#include <base/logging.h>
#include <base/posix/safe_strerror.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/time/time.h>
#include "cros-camera/common.h"
namespace cros {
namespace {
// The time to wait before HasDevice query times out.
constexpr base::TimeDelta kDeviceQueryTimeout =
base::TimeDelta::FromMilliseconds(1000);
const std::pair<std::string, cros::SensorHalClient::Location>
kLocationMapping[] = {
{"", cros::SensorHalClient::Location::kNone},
{mojom::kLocationBase, cros::SensorHalClient::Location::kBase},
{mojom::kLocationLid, cros::SensorHalClient::Location::kLid},
{mojom::kLocationCamera, cros::SensorHalClient::Location::kCamera},
};
bool IsSupported(cros::mojom::DeviceType type) {
switch (type) {
case cros::mojom::DeviceType::ACCEL:
return true;
case cros::mojom::DeviceType::ANGLVEL:
return true;
case cros::mojom::DeviceType::GRAVITY:
return true;
default:
return false;
}
}
base::Optional<mojom::DeviceType> ConvertDeviceType(
SensorHalClient::DeviceType type) {
switch (type) {
case SensorHalClient::DeviceType::kAccel:
return mojom::DeviceType::ACCEL;
case SensorHalClient::DeviceType::kAnglVel:
return mojom::DeviceType::ANGLVEL;
case SensorHalClient::DeviceType::kGravity:
return mojom::DeviceType::GRAVITY;
default:
return base::nullopt;
}
}
base::Optional<cros::SensorHalClient::Location> ParseLocation(
const base::Optional<std::string>& raw_location) {
if (!raw_location) {
LOGF(WARNING) << "No location attribute";
return cros::SensorHalClient::Location::kNone;
}
for (size_t i = 0; i < base::size(kLocationMapping); ++i) {
if (*raw_location == kLocationMapping[i].first)
return kLocationMapping[i].second;
}
return cros::SensorHalClient::Location::kNone;
}
} // namespace
// static
SensorHalClient* SensorHalClient::GetInstance(
CameraMojoChannelManagerToken* token) {
return CameraMojoChannelManager::FromToken(token)->GetSensorHalClient();
}
SensorHalClientImpl::SensorHalClientImpl(CameraMojoChannelManager* mojo_manager)
: mojo_manager_(mojo_manager),
cancellation_relay_(new CancellationRelay),
ipc_bridge_(new IPCBridge(mojo_manager_, cancellation_relay_.get())) {}
SensorHalClientImpl::~SensorHalClientImpl() {
bool result = mojo_manager_->GetIpcTaskRunner()->DeleteSoon(
FROM_HERE, std::move(ipc_bridge_));
DCHECK(result);
cancellation_relay_.reset();
}
bool SensorHalClientImpl::HasDevice(DeviceType type, Location location) {
base::Optional<mojom::DeviceType> device_type = ConvertDeviceType(type);
if (!device_type) {
return false;
}
auto future = cros::Future<bool>::Create(cancellation_relay_.get());
mojo_manager_->GetIpcTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&SensorHalClientImpl::IPCBridge::HasDevice,
ipc_bridge_->GetWeakPtr(), *device_type,
location, GetFutureCallback(future)));
if (!future->Wait())
return false;
return future->Get();
}
bool SensorHalClientImpl::RegisterSamplesObserver(
DeviceType type,
Location location,
double frequency,
SamplesObserver* samples_observer) {
base::Optional<mojom::DeviceType> device_type = ConvertDeviceType(type);
if (!device_type) {
return false;
}
if (frequency <= 0.0) {
LOGF(ERROR) << "Invalid frequency: " << frequency;
return false;
}
if (!samples_observer) {
LOGF(ERROR) << "Invalid SamplesObserver";
return false;
}
auto future = cros::Future<bool>::Create(cancellation_relay_.get());
mojo_manager_->GetIpcTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&SensorHalClientImpl::IPCBridge::RegisterSamplesObserver,
ipc_bridge_->GetWeakPtr(), *device_type, location,
frequency, samples_observer, GetFutureCallback(future)));
if (!future->Wait())
return false;
return future->Get();
}
void SensorHalClientImpl::UnregisterSamplesObserver(
SamplesObserver* samples_observer) {
if (!samples_observer) {
return;
}
mojo_manager_->GetIpcTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&SensorHalClientImpl::IPCBridge::UnregisterSamplesObserver,
ipc_bridge_->GetWeakPtr(), samples_observer));
}
SensorHalClientImpl::IPCBridge::IPCBridge(
CameraMojoChannelManager* mojo_manager,
CancellationRelay* cancellation_relay)
: mojo_manager_(mojo_manager),
cancellation_relay_(cancellation_relay),
ipc_task_runner_(mojo_manager_->GetIpcTaskRunner()) {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SensorHalClientImpl::IPCBridge::Start, GetWeakPtr()));
}
SensorHalClientImpl::IPCBridge::~IPCBridge() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
receiver_.reset();
ResetSensorService();
}
void SensorHalClientImpl::IPCBridge::Start() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(!receiver_.is_bound());
mojo_manager_->RegisterSensorHalClient(
receiver_.BindNewPipeAndPassRemote(),
base::BindOnce(&SensorHalClientImpl::IPCBridge::OnClientRegistered,
GetWeakPtr()),
base::BindOnce(&SensorHalClientImpl::IPCBridge::OnServiceMojoChannelError,
GetWeakPtr()));
receiver_.set_disconnect_handler(
base::BindOnce(&SensorHalClientImpl::IPCBridge::OnServiceMojoChannelError,
base::Unretained(this)));
}
void SensorHalClientImpl::IPCBridge::HasDevice(
mojom::DeviceType type,
Location location,
base::OnceCallback<void(bool)> callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (HasDeviceInternal(type, location)) {
std::move(callback).Run(true);
return;
}
if (AreAllDevicesOfTypeInitialized(type)) {
std::move(callback).Run(false);
return;
}
uint32_t info_id = device_query_info_counter_++;
DeviceQueryInfo info = {
.type = type, .location = location, .callback = std::move(callback)};
device_queries_info_.emplace(info_id, std::move(info));
// As there are devices uninitialized, wait for iioservice to report device
// info to us before the query times out.
ipc_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SensorHalClientImpl::IPCBridge::OnDeviceQueryTimedOut,
GetWeakPtr(), info_id),
kDeviceQueryTimeout);
}
void SensorHalClientImpl::IPCBridge::RegisterSamplesObserver(
mojom::DeviceType type,
Location location,
double frequency,
SamplesObserver* samples_observer,
base::OnceCallback<void(bool)> callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK_GT(frequency, 0.0);
DCHECK(samples_observer);
if (base::Contains(readers_, samples_observer)) {
LOGF(ERROR) << "This SamplesObserver is already registered to a device";
std::move(callback).Run(false);
return;
}
if (!HasDeviceInternal(type, location)) {
if (AreAllDevicesOfTypeInitialized(type)) {
LOGF(ERROR) << "Invalid DeviceType: " << type
<< " and Location: " << static_cast<int>(location) << " pair";
} else {
LOGF(ERROR) << "Not all devices with type: " << type
<< " have been initialized";
}
samples_observer->OnErrorOccurred(
SamplesObserver::ErrorType::DEVICE_REMOVED);
std::move(callback).Run(false);
return;
}
int32_t iio_device_id = device_maps_[type][location];
DCHECK(devices_[iio_device_id].scale.has_value());
ReaderData reader_data = {
.iio_device_id = iio_device_id,
.type = type,
.frequency = frequency,
.sensor_reader = std::make_unique<SensorReader>(
ipc_task_runner_, iio_device_id, type, frequency,
devices_[iio_device_id].scale.value(), samples_observer,
GetSensorDeviceRemote(iio_device_id))};
readers_.emplace(samples_observer, std::move(reader_data));
std::move(callback).Run(true);
}
void SensorHalClientImpl::IPCBridge::UnregisterSamplesObserver(
SamplesObserver* samples_observer) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(samples_observer);
readers_.erase(samples_observer);
}
void SensorHalClientImpl::IPCBridge::SetUpChannel(
mojo::PendingRemote<mojom::SensorService> pending_remote) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (IsReady()) {
LOGF(ERROR) << "Ignoring the second Remote<SensorService>";
return;
}
sensor_service_remote_.Bind(std::move(pending_remote));
sensor_service_remote_.set_disconnect_handler(
base::BindOnce(&SensorHalClientImpl::IPCBridge::OnSensorServiceDisconnect,
GetWeakPtr()));
sensor_service_remote_->RegisterNewDevicesObserver(
new_devices_observer_.BindNewPipeAndPassRemote());
new_devices_observer_.set_disconnect_handler(base::BindOnce(
&SensorHalClientImpl::IPCBridge::OnNewDevicesObserverDisconnect,
GetWeakPtr()));
sensor_service_remote_->GetAllDeviceIds(base::BindOnce(
&SensorHalClientImpl::IPCBridge::GetAllDeviceIdsCallback, GetWeakPtr()));
// Re-establish mojo channels for the existing observers with SensorReaders.
for (auto& [samples_observer, reader_data] : readers_) {
reader_data.sensor_reader = std::make_unique<SensorReader>(
ipc_task_runner_, reader_data.iio_device_id, reader_data.type,
reader_data.frequency,
devices_[reader_data.iio_device_id].scale.value(), samples_observer,
GetSensorDeviceRemote(reader_data.iio_device_id));
}
}
void SensorHalClientImpl::IPCBridge::OnNewDeviceAdded(
int32_t iio_device_id, const std::vector<mojom::DeviceType>& types) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (base::Contains(devices_, iio_device_id))
return;
RegisterDevice(iio_device_id, types);
}
base::WeakPtr<SensorHalClientImpl::IPCBridge>
SensorHalClientImpl::IPCBridge::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void SensorHalClientImpl::IPCBridge::GetAllDeviceIdsCallback(
const base::flat_map<int32_t, std::vector<mojom::DeviceType>>&
iio_device_ids_types) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
devices_retrieved_ = true;
for (const auto& [iio_device_id, types] : iio_device_ids_types) {
RegisterDevice(iio_device_id, types);
}
}
void SensorHalClientImpl::IPCBridge::OnDeviceQueryTimedOut(uint32_t info_id) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto it = device_queries_info_.find(info_id);
if (it == device_queries_info_.end()) {
// Task was already processed.
return;
}
LOGF(ERROR) << "HasDevice query timed out with type: " << it->second.type
<< ", and location: " << static_cast<int>(it->second.location);
std::move(it->second.callback).Run(false);
device_queries_info_.erase(it);
}
void SensorHalClientImpl::IPCBridge::RegisterDevice(
int32_t iio_device_id, const std::vector<mojom::DeviceType>& types) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DeviceData& device = devices_[iio_device_id];
if (device.ignored)
return;
// This should only be processed once.
if (device.types.empty()) {
for (const auto& type : types) {
if (IsSupported(type)) {
device.types.push_back(type);
}
}
}
// Not needed.
if (device.types.empty()) {
device.ignored = true;
return;
}
std::vector<std::string> attr_names;
if (!device.location) {
attr_names.push_back(mojom::kLocation);
}
if (!device.scale) {
attr_names.push_back(mojom::kScale);
}
if (attr_names.empty()) {
return;
}
device.remote = GetSensorDeviceRemote(iio_device_id);
// Add a temporary disconnect handler to catch failures during sensor
// enumeration. SensorDevice will handle disconnection during normal
// operation.
device.remote.set_disconnect_with_reason_handler(
base::BindOnce(&SensorHalClientImpl::IPCBridge::OnSensorDeviceDisconnect,
GetWeakPtr(), iio_device_id));
device.remote->GetAttributes(
attr_names,
base::BindOnce(&SensorHalClientImpl::IPCBridge::GetAttributesCallback,
GetWeakPtr(), iio_device_id, attr_names));
}
mojo::Remote<mojom::SensorDevice>
SensorHalClientImpl::IPCBridge::GetSensorDeviceRemote(int32_t iio_device_id) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(sensor_service_remote_.is_bound());
mojo::Remote<mojom::SensorDevice> sensor_device_remote;
auto& device = devices_[iio_device_id];
if (device.remote.is_bound()) {
// Reuse the previous remote.
sensor_device_remote = std::move(device.remote);
} else {
sensor_service_remote_->GetDevice(
iio_device_id, sensor_device_remote.BindNewPipeAndPassReceiver());
}
return sensor_device_remote;
}
void SensorHalClientImpl::IPCBridge::GetAttributesCallback(
int32_t iio_device_id,
const std::vector<std::string> attr_names,
const std::vector<base::Optional<std::string>>& values) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto it = devices_.find(iio_device_id);
DCHECK(it != devices_.end());
auto& device = it->second;
DCHECK(device.remote.is_bound());
if (attr_names.size() != values.size()) {
LOGF(ERROR) << "Size of attribute names: " << attr_names.size()
<< " doesn't match size of attribute values: " << values.size();
IgnoreDevice(iio_device_id);
}
for (size_t i = 0; i < attr_names.size(); ++i) {
if (attr_names[i] == mojom::kLocation && !device.location) {
device.location = ParseLocation(values[i]);
if (!device.location) {
LOGF(ERROR) << "Failed to parse location: " << values[i].value_or("")
<< ", with sensor id: " << iio_device_id;
IgnoreDevice(iio_device_id);
return;
}
} else if (attr_names[i] == mojom::kScale && !device.scale) {
double scale = 0.0;
if (!values[i] || !base::StringToDouble(*values[i], &scale)) {
LOGF(ERROR) << "Invalid scale: " << values[i].value_or("")
<< ", for device with id: " << iio_device_id;
// Assume the scale to be 1.
scale = 1.0;
}
device.scale = scale;
}
}
DCHECK(device.location && device.scale);
for (const auto& type : device.types) {
// TODO(b/189998208): Check how to choose from multiple devices with the
// same type and location pair.
if (!HasDeviceInternal(type, *device.location)) {
device_maps_[type][*device.location] = iio_device_id;
}
RunDeviceQueriesForType(type);
}
}
void SensorHalClientImpl::IPCBridge::IgnoreDevice(int32_t iio_device_id) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto& device = devices_[iio_device_id];
device.ignored = true;
device.remote.reset();
for (const auto& type : device.types) {
RunDeviceQueriesForType(type);
}
}
bool SensorHalClientImpl::IPCBridge::AreAllDevicesOfTypeInitialized(
mojom::DeviceType type) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (!devices_retrieved_) {
return false;
}
for (auto& [iio_device_id, device] : devices_) {
if (device.ignored || !base::Contains(device.types, type))
continue;
if (!device.location)
return false;
}
return true;
}
void SensorHalClientImpl::IPCBridge::RunDeviceQueriesForType(
mojom::DeviceType type) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
bool all_initialized = AreAllDevicesOfTypeInitialized(type);
for (auto it = device_queries_info_.begin();
it != device_queries_info_.end();) {
bool processed = false;
if (type == it->second.type) {
if (HasDeviceInternal(it->second.type, it->second.location)) {
std::move(it->second.callback).Run(true);
processed = true;
} else if (all_initialized) {
std::move(it->second.callback).Run(false);
processed = true;
}
}
if (processed) {
it = device_queries_info_.erase(it);
} else {
++it;
}
}
}
bool SensorHalClientImpl::IPCBridge::HasDeviceInternal(mojom::DeviceType type,
Location location) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
return base::Contains(device_maps_, type) &&
base::Contains(device_maps_[type], location);
}
void SensorHalClientImpl::IPCBridge::OnClientRegistered(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (result == 0)
return;
LOGF(ERROR) << "Failed to register sensor HAL: "
<< base::safe_strerror(-result);
OnServiceMojoChannelError();
}
void SensorHalClientImpl::IPCBridge::OnServiceMojoChannelError() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
// The CameraHalDispatcher Mojo parent is probably dead. We need to restart
// another process in order to connect to the new Mojo parent.
LOGF(INFO) << "Mojo connection to SensorHalDispatcher is broken";
cancellation_relay_->CancelAllFutures();
receiver_.reset();
ResetSensorService();
for (auto& [samples_observer, reader_data] : readers_) {
samples_observer->OnErrorOccurred(
SamplesObserver::ErrorType::MOJO_DISCONNECTED);
}
}
void SensorHalClientImpl::IPCBridge::ResetSensorService() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
for (auto& [id, device] : devices_) {
// Only reset the mojo pipe and keep all the other initialized types and
// attributes, so that it won't need to be initialized twice when iioservice
// restarts and the mojo connection is re-established.
device.remote.reset();
}
new_devices_observer_.reset();
sensor_service_remote_.reset();
for (auto& [samples_observer, reader_data] : readers_) {
reader_data.sensor_reader.reset();
}
}
void SensorHalClientImpl::IPCBridge::OnSensorServiceDisconnect() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
LOGF(ERROR) << "Wait for IIO Service's reconnection.";
cancellation_relay_->CancelAllFutures();
ResetSensorService();
}
void SensorHalClientImpl::IPCBridge::OnNewDevicesObserverDisconnect() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
LOGF(ERROR) << "Wait for IIO Service's reconnection.";
// Assumes IIO Service has crashed and waits for its relaunch.
ResetSensorService();
}
void SensorHalClientImpl::IPCBridge::OnSensorDeviceDisconnect(
int32_t iio_device_id,
uint32_t custom_reason_code,
const std::string& description) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
const auto reason = static_cast<cros::mojom::SensorDeviceDisconnectReason>(
custom_reason_code);
LOG(WARNING) << "SensorDevice disconnected with id: " << iio_device_id
<< ", reason: " << reason << ", description: " << description;
switch (reason) {
case cros::mojom::SensorDeviceDisconnectReason::IIOSERVICE_CRASHED:
ResetSensorService();
break;
case cros::mojom::SensorDeviceDisconnectReason::DEVICE_REMOVED:
for (auto it = readers_.begin(); it != readers_.end();) {
if (it->second.iio_device_id == iio_device_id) {
it->first->OnErrorOccurred(
SamplesObserver::ErrorType::DEVICE_REMOVED);
it = readers_.erase(it);
} else {
++it;
}
}
auto types = devices_[iio_device_id].types;
auto location_opt = devices_[iio_device_id].location;
devices_.erase(iio_device_id);
if (!location_opt.has_value())
break;
auto location = location_opt.value();
for (const auto& type : types) {
auto& map_type = device_maps_[type];
if (map_type[location] == iio_device_id) {
map_type.erase(location);
// TODO(b/189998208): Check how to choose from multiple devices with
// the same type and location pair.
for (auto& device : devices_) {
if (base::Contains(device.second.types, type) &&
device.second.location == location) {
map_type[location] = device.first;
RunDeviceQueriesForType(type);
break;
}
}
}
}
break;
}
}
} // namespace cros