blob: ef4e2d39fc2751b68cea271b77972c772eeacd30 [file]
/*
* Copyright 2021 The ChromiumOS Authors
* 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 <algorithm>
#include <iterator>
#include <optional>
#include <utility>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/posix/safe_strerror.h>
#include <base/strings/string_number_conversions.h>
#include <base/time/time.h>
#include <chromeos/mojo/service_constants.h>
#include "cros-camera/common.h"
namespace cros {
namespace {
// The time to wait before HasDevice query times out.
constexpr base::TimeDelta kDeviceQueryTimeout = base::Milliseconds(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;
}
}
std::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 std::nullopt;
}
}
std::optional<cros::SensorHalClient::Location> ParseLocation(
const std::optional<std::string>& raw_location) {
if (!raw_location) {
LOGF(WARNING) << "No location attribute";
return cros::SensorHalClient::Location::kNone;
}
for (const auto& mapping : kLocationMapping) {
if (*raw_location == mapping.first) {
return mapping.second;
}
}
return cros::SensorHalClient::Location::kNone;
}
} // namespace
// static
SensorHalClient* SensorHalClient::GetInstance(
CameraMojoChannelManagerToken* token) {
return CameraMojoChannelManager::FromToken(token)->GetSensorHalClient();
}
SensorHalClientImpl::SensorHalClientImpl(CameraMojoChannelManager* mojo_manager)
: cancellation_relay_(new CancellationRelay),
ipc_bridge_(mojo_manager->GetIpcTaskRunner(), mojo_manager) {}
SensorHalClientImpl::~SensorHalClientImpl() {
ipc_bridge_.Reset();
cancellation_relay_.reset();
}
bool SensorHalClientImpl::HasDevice(DeviceType type, Location location) {
std::optional<mojom::DeviceType> device_type = ConvertDeviceType(type);
if (!device_type) {
return false;
}
auto future = cros::Future<bool>::Create(cancellation_relay_.get());
ipc_bridge_.AsyncCall(&SensorHalClientImpl::IPCBridge::HasDevice)
.WithArgs(*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) {
std::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());
ipc_bridge_
.AsyncCall(&SensorHalClientImpl::IPCBridge::RegisterSamplesObserver)
.WithArgs(*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;
}
ipc_bridge_
.AsyncCall(&SensorHalClientImpl::IPCBridge::UnregisterSamplesObserver)
.WithArgs(samples_observer);
}
SensorHalClientImpl::IPCBridge::IPCBridge(
CameraMojoChannelManager* mojo_manager)
: mojo_manager_(mojo_manager) {
mojo_service_manager_observer_ =
mojo_manager_->CreateMojoServiceManagerObserver(
/*service_name=*/chromeos::mojo_services::kIioSensor,
/*on_register_callback=*/
base::BindRepeating(&SensorHalClientImpl::IPCBridge::RequestService,
GetWeakPtr()),
/*on_unregister_callback=*/
base::BindRepeating(
&SensorHalClientImpl::IPCBridge::OnUnregisterCallback,
GetWeakPtr()));
}
SensorHalClientImpl::IPCBridge::~IPCBridge() {
ResetSensorService();
}
void SensorHalClientImpl::IPCBridge::RequestService() {
mojo::PendingRemote<cros::mojom::SensorService> sensor_service_remote;
mojo_manager_->RequestServiceFromMojoServiceManager(
/*service_name=*/chromeos::mojo_services::kIioSensor,
sensor_service_remote.InitWithNewPipeAndPassReceiver().PassPipe());
SetUpChannel(std::move(sensor_service_remote));
}
void SensorHalClientImpl::IPCBridge::OnUnregisterCallback() {
LOGF(WARNING)
<< "IioSensor service is no longer registered in mojo service manager.";
}
void SensorHalClientImpl::IPCBridge::HasDevice(
mojom::DeviceType type,
Location location,
base::OnceCallback<void(bool)> callback) {
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.
base::SequencedTaskRunner::GetCurrentDefault()->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_GT(frequency, 0.0);
DCHECK(samples_observer);
if (readers_.contains(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());
// If iioservice is not connected, delay constructing SensorReader.
ReaderData reader_data = {
.iio_device_id = iio_device_id,
.type = type,
.frequency = frequency,
.sensor_reader =
sensor_service_remote_.is_bound()
? std::make_unique<SensorReader>(
iio_device_id, type, frequency,
devices_[iio_device_id].scale.value(), samples_observer,
GetSensorDeviceRemote(iio_device_id))
: nullptr};
readers_.emplace(samples_observer, std::move(reader_data));
std::move(callback).Run(true);
}
void SensorHalClientImpl::IPCBridge::UnregisterSamplesObserver(
SamplesObserver* samples_observer) {
DCHECK(samples_observer);
readers_.erase(samples_observer);
}
void SensorHalClientImpl::IPCBridge::SetUpChannel(
mojo::PendingRemote<mojom::SensorService> pending_remote) {
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>(
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) {
if (devices_.contains(iio_device_id)) {
return;
}
RegisterDevice(iio_device_id, types);
}
void SensorHalClientImpl::IPCBridge::OnDeviceRemoved(int32_t iio_device_id) {
LOGF(INFO) << "OnDeviceRemoved: " << iio_device_id;
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;
}
}
// Look for replacement sensors for the same types & location.
std::vector<mojom::DeviceType> types = devices_[iio_device_id].types;
std::optional<Location> location_opt = devices_[iio_device_id].location;
devices_.erase(iio_device_id);
if (!location_opt.has_value()) {
return;
}
auto location = location_opt.value();
for (const mojom::DeviceType& type : types) {
auto& map_type = device_maps_[type];
if (map_type[location] == iio_device_id) {
map_type.erase(location);
// Currently we couldn't differentiate devices with the same type and
// location.
for (auto& device : devices_) {
if (std::ranges::contains(device.second.types, type) &&
device.second.location == location) {
map_type[location] = device.first;
RunDeviceQueriesForType(type);
break;
}
}
}
}
}
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) {
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) {
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) {
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);
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(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<std::optional<std::string>>& values) {
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) {
// Currently we couldn't differentiate devices with the same type and
// location.
if (!HasDeviceInternal(type, *device.location)) {
device_maps_[type][*device.location] = iio_device_id;
}
RunDeviceQueriesForType(type);
}
}
void SensorHalClientImpl::IPCBridge::IgnoreDevice(int32_t iio_device_id) {
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) {
if (!devices_retrieved_) {
return false;
}
for (auto& [iio_device_id, device] : devices_) {
if (device.ignored || !std::ranges::contains(device.types, type)) {
continue;
}
if (!device.location) {
return false;
}
}
return true;
}
void SensorHalClientImpl::IPCBridge::RunDeviceQueriesForType(
mojom::DeviceType type) {
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) {
return device_maps_.contains(type) && device_maps_[type].contains(location);
}
void SensorHalClientImpl::IPCBridge::ResetSensorService() {
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() {
LOGF(ERROR) << "Wait for IIO Service's reconnection.";
ResetSensorService();
}
void SensorHalClientImpl::IPCBridge::OnNewDevicesObserverDisconnect() {
LOGF(ERROR) << "Wait for IIO Service's reconnection.";
// Assumes IIO Service has crashed and waits for its relaunch.
ResetSensorService();
}
} // namespace cros