| // 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 "iioservice/daemon/sensor_device_fusion.h" |
| |
| #include <utility> |
| |
| #include <base/strings/stringprintf.h> |
| #include <libmems/common_types.h> |
| |
| #include "iioservice/daemon/sensor_metrics.h" |
| #include "iioservice/include/common.h" |
| |
| namespace iioservice { |
| |
| void SensorDeviceFusion::SensorDeviceFusionDeleter(SensorDeviceFusion* device) { |
| if (device == nullptr) |
| return; |
| |
| if (!device->ipc_task_runner_->RunsTasksInCurrentSequence()) { |
| device->ipc_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SensorDeviceFusion::SensorDeviceFusionDeleter, device)); |
| return; |
| } |
| |
| delete device; |
| } |
| |
| void SensorDeviceFusion::AddReceiver( |
| mojo::PendingReceiver<cros::mojom::SensorDevice> request) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| mojo::ReceiverId id = |
| receiver_set_.Add(this, std::move(request), ipc_task_runner_); |
| |
| clients_.emplace(id, ClientData(id)); |
| } |
| |
| void SensorDeviceFusion::SetTimeout(uint32_t timeout) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| auto it = clients_.find(id); |
| if (it == clients_.end()) |
| return; |
| |
| it->second.timeout = timeout; |
| } |
| |
| void SensorDeviceFusion::SetFrequency(double frequency, |
| SetFrequencyCallback callback) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(samples_handler_); |
| |
| if (invalid_) { |
| std::move(callback).Run(-1.0); |
| return; |
| } |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| auto it = clients_.find(id); |
| if (it == clients_.end()) { |
| std::move(callback).Run(-1.0); |
| return; |
| } |
| |
| frequency = FixFrequency(frequency); |
| std::move(callback).Run(frequency); |
| |
| ClientData& client = it->second; |
| if (client.observer.is_bound()) { |
| // Let |samples_handler_| update |client.frequency|. |
| samples_handler_->UpdateFrequency(&client, frequency); |
| } else { |
| client.frequency = frequency; |
| } |
| } |
| |
| void SensorDeviceFusion::StartReadingSamples( |
| mojo::PendingRemote<cros::mojom::SensorDeviceSamplesObserver> observer) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(samples_handler_); |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| auto it = clients_.find(id); |
| if (it == clients_.end()) |
| return; |
| ClientData& client = it->second; |
| |
| samples_handler_->AddClient(&client, std::move(observer)); |
| } |
| |
| void SensorDeviceFusion::StopReadingSamples() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| StopReadingSamplesOnClient(id); |
| } |
| |
| void SensorDeviceFusion::GetAllChannelIds(GetAllChannelIdsCallback callback) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| std::move(callback).Run(channel_ids_); |
| } |
| |
| void SensorDeviceFusion::SetChannelsEnabled( |
| const std::vector<int32_t>& iio_chn_indices, |
| bool en, |
| SetChannelsEnabledCallback callback) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (invalid_) { |
| std::move(callback).Run(iio_chn_indices); |
| return; |
| } |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| auto it = clients_.find(id); |
| if (it == clients_.end()) { |
| LOGF(ERROR) << "Failed to find clients with id: " << id; |
| std::move(callback).Run(iio_chn_indices); |
| return; |
| } |
| |
| ClientData& client = it->second; |
| |
| if (en) { |
| for (int32_t chn_index : iio_chn_indices) |
| client.enabled_chn_indices.emplace(chn_index); |
| } else { |
| for (int32_t chn_index : iio_chn_indices) |
| client.enabled_chn_indices.erase(chn_index); |
| } |
| |
| std::move(callback).Run({}); |
| } |
| |
| void SensorDeviceFusion::GetChannelsEnabled( |
| const std::vector<int32_t>& iio_chn_indices, |
| GetChannelsEnabledCallback callback) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| auto it = clients_.find(id); |
| if (it == clients_.end()) { |
| LOGF(ERROR) << "Failed to find clients with id: " << id; |
| std::move(callback).Run(std::vector<bool>(iio_chn_indices.size(), false)); |
| return; |
| } |
| |
| ClientData& client = it->second; |
| |
| // List of channels enabled. |
| std::vector<bool> enabled; |
| |
| for (int32_t chn_index : iio_chn_indices) { |
| enabled.push_back(client.enabled_chn_indices.find(chn_index) != |
| client.enabled_chn_indices.end()); |
| } |
| |
| std::move(callback).Run(std::move(enabled)); |
| } |
| |
| SensorDeviceFusion::IioDeviceHandler::IioDeviceHandler( |
| scoped_refptr<base::SequencedTaskRunner> ipc_task_runner, |
| int32_t iio_device_id, |
| cros::mojom::DeviceType type, |
| base::RepeatingCallback< |
| void(int32_t iio_device_id, |
| mojo::PendingReceiver<cros::mojom::SensorDevice> request, |
| const std::set<cros::mojom::DeviceType>& types)> |
| iio_add_receiver_callback, |
| base::RepeatingCallback<void(std::vector<int64_t>)> |
| on_sample_updated_callback, |
| base::RepeatingCallback<void()> on_read_failed_callback, |
| base::OnceCallback<void()> invalidate_callback) |
| : ipc_task_runner_(std::move(ipc_task_runner)), |
| iio_device_id_(iio_device_id), |
| type_(type), |
| on_sample_updated_callback_(std::move(on_sample_updated_callback)), |
| on_read_failed_callback_(std::move(on_read_failed_callback)), |
| invalidate_callback_(std::move(invalidate_callback)) { |
| iio_add_receiver_callback.Run(iio_device_id_, |
| remote_.BindNewPipeAndPassReceiver(), {type_}); |
| |
| remote_.set_disconnect_handler(base::BindOnce( |
| &SensorDeviceFusion::IioDeviceHandler::OnIioDeviceDisconnect, |
| weak_factory_.GetWeakPtr())); |
| |
| SetChannelIds(); |
| remote_->GetAllChannelIds(base::BindOnce( |
| &SensorDeviceFusion::IioDeviceHandler::GetAllChannelIdsCallback, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::SetAttribute( |
| std::string attr_name, base::Optional<std::string> attr_value) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| attributes_[attr_name] = attr_value; |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::SetFrequency( |
| double frequency, base::OnceCallback<void(double)> callback) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| if (!remote_.is_bound()) |
| return; |
| |
| remote_->SetFrequency( |
| frequency, |
| base::BindOnce( |
| &SensorDeviceFusion::IioDeviceHandler::SetFrequencyCallback, |
| weak_factory_.GetWeakPtr(), frequency, std::move(callback))); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::GetAttributes( |
| const std::vector<std::string>& attr_names, |
| cros::mojom::SensorDevice::GetAttributesCallback callback) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| if (!remote_.is_bound()) { |
| GetAttributesCallback(attr_names, std::move(callback), |
| std::vector<base::Optional<std::string>>( |
| attr_names.size(), base::nullopt)); |
| return; |
| } |
| |
| remote_->GetAttributes( |
| attr_names, |
| base::BindOnce( |
| &SensorDeviceFusion::IioDeviceHandler::GetAttributesCallback, |
| weak_factory_.GetWeakPtr(), attr_names, std::move(callback))); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::OnSampleUpdated( |
| const base::flat_map<int32_t, int64_t>& sample) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| std::vector<int64_t> values; |
| for (int32_t index : channel_indices_) { |
| auto it = sample.find(index); |
| if (it == sample.end()) { |
| LOGF(ERROR) << "Couldn't find index: " << index |
| << ", in the sample of iio device with type: " << type_; |
| on_read_failed_callback_.Run(); |
| return; |
| } |
| |
| values.push_back(it->second); |
| } |
| |
| on_sample_updated_callback_.Run(std::move(values)); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::OnErrorOccurred( |
| cros::mojom::ObserverErrorType type) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| switch (type) { |
| case cros::mojom::ObserverErrorType::ALREADY_STARTED: |
| LOGF(ERROR) << "Device " << iio_device_id_ |
| << ": Another observer has already started to read samples"; |
| Invalidate(); |
| break; |
| |
| case cros::mojom::ObserverErrorType::FREQUENCY_INVALID: |
| // Ignore: We might start reading without a valid frequency set. |
| break; |
| |
| case cros::mojom::ObserverErrorType::NO_ENABLED_CHANNELS: |
| LOGF(ERROR) << "Device " << iio_device_id_ |
| << ": Observer started with no channels enabled"; |
| Invalidate(); |
| break; |
| |
| case cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED: |
| LOGF(ERROR) << "Device " << iio_device_id_ |
| << ": Failed to set frequency to the physical device"; |
| break; |
| |
| case cros::mojom::ObserverErrorType::GET_FD_FAILED: |
| LOGF(ERROR) << "Device " << iio_device_id_ |
| << ": Failed to get the device's fd to poll on"; |
| break; |
| |
| case cros::mojom::ObserverErrorType::READ_FAILED: |
| LOGF(ERROR) << "Device " << iio_device_id_ << ": Failed to read a sample"; |
| on_read_failed_callback_.Run(); |
| break; |
| |
| case cros::mojom::ObserverErrorType::READ_TIMEOUT: |
| LOGF(ERROR) << "Device " << iio_device_id_ << ": A read timed out"; |
| break; |
| |
| default: |
| LOGF(ERROR) << "Device " << iio_device_id_ << ": error " << type; |
| break; |
| } |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::DisableSamples() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (remote_.is_bound()) |
| remote_->StopReadingSamples(); |
| |
| receiver_.reset(); |
| |
| invalidate_callback_.Reset(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::Invalidate() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (invalidate_callback_) |
| std::move(invalidate_callback_).Run(); |
| |
| DisableSamples(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::SetChannelIds() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| switch (type_) { |
| case cros::mojom::DeviceType::ACCEL: |
| for (char axis : kChannelAxes) { |
| channel_ids_.push_back(base::StringPrintf( |
| kChannelFormat, cros::mojom::kAccelerometerChannel, axis)); |
| } |
| channel_ids_.push_back(cros::mojom::kTimestampChannel); |
| break; |
| |
| case cros::mojom::DeviceType::ANGLVEL: |
| for (char axis : kChannelAxes) { |
| channel_ids_.push_back(base::StringPrintf( |
| kChannelFormat, cros::mojom::kGyroscopeChannel, axis)); |
| } |
| channel_ids_.push_back(cros::mojom::kTimestampChannel); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::OnIioDeviceDisconnect() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| LOGF(ERROR) << "OnIioDeviceDisconnect in fusion device with iio_device_id: " |
| << iio_device_id_ << ", and iio device's type: " << type_; |
| |
| remote_.reset(); |
| Invalidate(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::OnObserverDisconnect() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| LOGF(ERROR) << "OnObserverDisconnect in fusion device with iio_device_id: " |
| << iio_device_id_ << ", and iio device's type: " << type_; |
| |
| Invalidate(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::SetFrequencyCallback( |
| double requested_frequency, |
| cros::mojom::SensorDevice::SetFrequencyCallback callback, |
| double result_frequency) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if ((requested_frequency > 0 && result_frequency > 0) || |
| (requested_frequency <= 0 && result_frequency <= 0)) { |
| if (callback) |
| std::move(callback).Run(result_frequency); |
| |
| return; |
| } |
| |
| LOGF(ERROR) << "Failed to set requested_frequency: " << requested_frequency; |
| Invalidate(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::GetAttributesCallback( |
| const std::vector<std::string>& attr_names, |
| cros::mojom::SensorDevice::GetAttributesCallback callback, |
| const std::vector<base::Optional<std::string>>& values) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK_EQ(attr_names.size(), values.size()); |
| |
| std::vector<base::Optional<std::string>> overriden_values = values; |
| for (size_t i = 0; i < attr_names.size(); ++i) { |
| auto it = attributes_.find(attr_names[i]); |
| if (it == attributes_.end()) |
| continue; |
| |
| overriden_values[i] = it->second; |
| } |
| |
| std::move(callback).Run(std::move(overriden_values)); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::GetAllChannelIdsCallback( |
| const std::vector<std::string>& iio_chn_ids) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(remote_.is_bound()); |
| // Should only be called once. |
| DCHECK(!receiver_.is_bound()); |
| |
| for (const auto& channel_id : channel_ids_) { |
| bool found = false; |
| for (int i = 0; i < iio_chn_ids.size(); ++i) { |
| if (channel_id == iio_chn_ids[i]) { |
| found = true; |
| channel_indices_.push_back(i); |
| break; |
| } |
| } |
| |
| if (!found) { |
| LOGF(ERROR) << "Couldn't find channel with id: " << channel_id |
| << ", in the iio device with id: " << iio_device_id_; |
| Invalidate(); |
| return; |
| } |
| } |
| |
| StartReading(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::SetChannelsEnabledCallback( |
| const std::vector<int32_t>& failed_indices) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| for (int32_t index : failed_indices) { |
| LOGF(ERROR) << "Failed to enable channel with index: " << index |
| << ", in iio device with id: " << iio_device_id_; |
| } |
| |
| if (!failed_indices.empty()) |
| Invalidate(); |
| } |
| |
| void SensorDeviceFusion::IioDeviceHandler::StartReading() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(remote_.is_bound()); |
| DCHECK_EQ(channel_ids_.size(), channel_indices_.size()); |
| |
| if (!invalidate_callback_) { |
| // This handler has already been invalidated. No sample is required. |
| return; |
| } |
| |
| remote_->SetTimeout(0); |
| remote_->SetChannelsEnabled( |
| channel_indices_, true, |
| base::BindOnce( |
| &SensorDeviceFusion::IioDeviceHandler::SetChannelsEnabledCallback, |
| weak_factory_.GetWeakPtr())); |
| |
| remote_->StartReadingSamples(receiver_.BindNewPipeAndPassRemote()); |
| receiver_.set_disconnect_handler(base::BindOnce( |
| &SensorDeviceFusion::IioDeviceHandler::OnObserverDisconnect, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| SensorDeviceFusion::SensorDeviceFusion( |
| int32_t id, |
| cros::mojom::DeviceType type, |
| Location location, |
| scoped_refptr<base::SequencedTaskRunner> ipc_task_runner, |
| base::RepeatingCallback< |
| void(int32_t iio_device_id, |
| mojo::PendingReceiver<cros::mojom::SensorDevice> request, |
| const std::set<cros::mojom::DeviceType>& types)> |
| iio_add_receiver_callback, |
| double max_frequency, |
| std::vector<std::string> channel_ids) |
| : id_(id), |
| type_(type), |
| location_(location), |
| ipc_task_runner_(std::move(ipc_task_runner)), |
| iio_add_receiver_callback_(std::move(iio_add_receiver_callback)), |
| max_frequency_(max_frequency), |
| channel_ids_(std::move(channel_ids)) { |
| receiver_set_.set_disconnect_handler( |
| base::BindRepeating(&SensorDeviceFusion::OnSensorDeviceDisconnect, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void SensorDeviceFusion::Invalidate() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| LOGF(ERROR) << "Invalidating fusion device and prohibit settings and samples " |
| "with type: " |
| << type_ << ", and location: " << location_; |
| |
| invalid_ = true; |
| samples_handler_->Invalidate(); |
| for (auto& iio_device_handler : iio_device_handlers_) |
| iio_device_handler->DisableSamples(); |
| } |
| |
| void SensorDeviceFusion::UpdateRequestedFrequency(double frequency) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| requested_frequency_ = frequency; |
| SensorMetrics::GetInstance()->SendSensorUsage(id_, requested_frequency_); |
| } |
| |
| void SensorDeviceFusion::OnSensorDeviceDisconnect() { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| mojo::ReceiverId id = receiver_set_.current_receiver(); |
| |
| LOGF(INFO) << "SensorDevice disconnected. ReceiverId: " << id; |
| StopReadingSamplesOnClient(id); |
| |
| clients_.erase(id); |
| } |
| |
| void SensorDeviceFusion::StopReadingSamplesOnClient(mojo::ReceiverId id) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(samples_handler_); |
| |
| auto it = clients_.find(id); |
| if (it == clients_.end()) |
| return; |
| |
| ClientData& client = it->second; |
| samples_handler_->RemoveClient(&client); |
| } |
| |
| double SensorDeviceFusion::FixFrequency(double frequency) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (frequency < libmems::kFrequencyEpsilon) |
| return 0.0; |
| |
| if (frequency > max_frequency_) |
| return max_frequency_; |
| |
| return frequency; |
| } |
| |
| double SensorDeviceFusion::FixFrequencyWithMin(double min_frequency, |
| double frequency) { |
| DCHECK(ipc_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (frequency < libmems::kFrequencyEpsilon) |
| return 0.0; |
| |
| if (frequency < min_frequency) |
| return min_frequency; |
| |
| if (frequency > max_frequency_) |
| return max_frequency_; |
| |
| return frequency; |
| } |
| |
| } // namespace iioservice |