|  | // Copyright 2019 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 "libmems/test_fakes.h" | 
|  |  | 
|  | #include <sys/eventfd.h> | 
|  |  | 
|  | #include <base/files/file_util.h> | 
|  | #include <base/logging.h> | 
|  | #include "base/posix/eintr_wrapper.h" | 
|  | #include <base/stl_util.h> | 
|  |  | 
|  | #include "libmems/common_types.h" | 
|  |  | 
|  | namespace libmems { | 
|  | namespace fakes { | 
|  |  | 
|  | FakeIioChannel::FakeIioChannel(const std::string& id, bool enabled) | 
|  | : id_(id), enabled_(enabled) {} | 
|  |  | 
|  | void FakeIioChannel::SetEnabled(bool en) { | 
|  | enabled_ = en; | 
|  | } | 
|  |  | 
|  | bool FakeIioChannel::SetScanElementsEnabled(bool en) { | 
|  | scan_elements_enabled_ = en; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | base::Optional<T> FakeReadAttributes(const std::string& name, | 
|  | std::map<std::string, T> attributes) { | 
|  | auto k = attributes.find(name); | 
|  | if (k == attributes.end()) | 
|  | return base::nullopt; | 
|  | return k->second; | 
|  | } | 
|  |  | 
|  | base::Optional<std::string> FakeIioChannel::ReadStringAttribute( | 
|  | const std::string& name) const { | 
|  | return FakeReadAttributes<>(name, text_attributes_); | 
|  | } | 
|  | base::Optional<int64_t> FakeIioChannel::ReadNumberAttribute( | 
|  | const std::string& name) const { | 
|  | return FakeReadAttributes<>(name, numeric_attributes_); | 
|  | } | 
|  | base::Optional<double> FakeIioChannel::ReadDoubleAttribute( | 
|  | const std::string& name) const { | 
|  | return FakeReadAttributes<>(name, double_attributes_); | 
|  | } | 
|  |  | 
|  | bool FakeIioChannel::WriteStringAttribute(const std::string& name, | 
|  | const std::string& value) { | 
|  | text_attributes_[name] = value; | 
|  | return true; | 
|  | } | 
|  | bool FakeIioChannel::WriteNumberAttribute(const std::string& name, | 
|  | int64_t value) { | 
|  | numeric_attributes_[name] = value; | 
|  | return true; | 
|  | } | 
|  | bool FakeIioChannel::WriteDoubleAttribute(const std::string& name, | 
|  | double value) { | 
|  | double_attributes_[name] = value; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | base::Optional<int64_t> FakeIioChannel::GetData(int index) { | 
|  | if (!enabled_ || index < 0 || index >= base::size(kFakeAccelSamples)) | 
|  | return base::nullopt; | 
|  |  | 
|  | auto raw = ReadNumberAttribute(kRawAttr); | 
|  | if (raw.has_value()) | 
|  | return raw; | 
|  |  | 
|  | for (int i = 0; i < base::size(kFakeAccelChns); ++i) { | 
|  | if (id_.compare(kFakeAccelChns[i]) == 0) | 
|  | return kFakeAccelSamples[index][i]; | 
|  | } | 
|  |  | 
|  | return base::nullopt; | 
|  | } | 
|  |  | 
|  | FakeIioDevice::FakeIioDevice(FakeIioContext* ctx, | 
|  | const std::string& name, | 
|  | int id) | 
|  | : IioDevice(), context_(ctx), name_(name), id_(id) {} | 
|  |  | 
|  | base::FilePath FakeIioDevice::GetPath() const { | 
|  | std::string id_str(kDeviceIdPrefix); | 
|  | id_str.append(std::to_string(GetId())); | 
|  | return base::FilePath(kSysDevString).Append(id_str); | 
|  | } | 
|  |  | 
|  | base::Optional<std::string> FakeIioDevice::ReadStringAttribute( | 
|  | const std::string& name) const { | 
|  | if (name.compare(kDeviceName) == 0) | 
|  | return name_; | 
|  | return FakeReadAttributes<>(name, text_attributes_); | 
|  | } | 
|  | base::Optional<int64_t> FakeIioDevice::ReadNumberAttribute( | 
|  | const std::string& name) const { | 
|  | return FakeReadAttributes<>(name, numeric_attributes_); | 
|  | } | 
|  | base::Optional<double> FakeIioDevice::ReadDoubleAttribute( | 
|  | const std::string& name) const { | 
|  | return FakeReadAttributes<>(name, double_attributes_); | 
|  | } | 
|  |  | 
|  | bool FakeIioDevice::WriteStringAttribute(const std::string& name, | 
|  | const std::string& value) { | 
|  | text_attributes_[name] = value; | 
|  | return true; | 
|  | } | 
|  | bool FakeIioDevice::WriteNumberAttribute(const std::string& name, | 
|  | int64_t value) { | 
|  | numeric_attributes_[name] = value; | 
|  | return true; | 
|  | } | 
|  | bool FakeIioDevice::WriteDoubleAttribute(const std::string& name, | 
|  | double value) { | 
|  | double_attributes_[name] = value; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FakeIioDevice::SetTrigger(IioDevice* trigger) { | 
|  | trigger_ = trigger; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FakeIioDevice::EnableBuffer(size_t n) { | 
|  | buffer_length_ = n; | 
|  | buffer_enabled_ = true; | 
|  | return true; | 
|  | } | 
|  | bool FakeIioDevice::DisableBuffer() { | 
|  | buffer_enabled_ = false; | 
|  | return true; | 
|  | } | 
|  | bool FakeIioDevice::IsBufferEnabled(size_t* n) const { | 
|  | if (n && buffer_enabled_) | 
|  | *n = buffer_length_; | 
|  | return buffer_enabled_; | 
|  | } | 
|  |  | 
|  | base::Optional<int32_t> FakeIioDevice::GetBufferFd() { | 
|  | if (disabled_fd_) | 
|  | return base::nullopt; | 
|  |  | 
|  | if (!CreateBuffer()) | 
|  | return base::nullopt; | 
|  |  | 
|  | return sample_fd_.get(); | 
|  | } | 
|  | base::Optional<IioDevice::IioSample> FakeIioDevice::ReadSample() { | 
|  | if (is_paused_ || disabled_fd_) | 
|  | return base::nullopt; | 
|  |  | 
|  | if (!failed_read_queue_.empty()) { | 
|  | CHECK_GE(failed_read_queue_.top(), sample_index_); | 
|  | if (failed_read_queue_.top() == sample_index_) { | 
|  | failed_read_queue_.pop(); | 
|  | return base::nullopt; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!CreateBuffer()) | 
|  | return base::nullopt; | 
|  |  | 
|  | if (!ReadByte()) | 
|  | return base::nullopt; | 
|  |  | 
|  | base::Optional<double> freq_opt = ReadDoubleAttribute(kSamplingFrequencyAttr); | 
|  | if (!freq_opt.has_value()) { | 
|  | LOG(ERROR) << "sampling_frequency not set"; | 
|  | return base::nullopt; | 
|  | } | 
|  | double frequency = freq_opt.value(); | 
|  | if (frequency <= 0.0) { | 
|  | LOG(ERROR) << "Invalid frequency: " << frequency; | 
|  | return base::nullopt; | 
|  | } | 
|  |  | 
|  | IioDevice::IioSample sample; | 
|  | auto channels = GetAllChannels(); | 
|  | for (int32_t i = 0; i < channels.size(); ++i) { | 
|  | FakeIioChannel* chn = dynamic_cast<FakeIioChannel*>(channels[i]); | 
|  | auto value = chn->GetData(sample_index_); | 
|  | if (!value.has_value()) { | 
|  | LOG(ERROR) << "Channel: " << channels_[i].chn_id << " has no sample"; | 
|  | return base::nullopt; | 
|  | } | 
|  |  | 
|  | sample[i] = value.value(); | 
|  | } | 
|  |  | 
|  | sample_index_ += 1; | 
|  |  | 
|  | if (sample_index_ < base::size(kFakeAccelSamples)) { | 
|  | if (pause_index_.has_value() && sample_index_ == pause_index_.value()) | 
|  | SetPause(); | 
|  | else if (!WriteByte()) | 
|  | return base::nullopt; | 
|  | } | 
|  |  | 
|  | return sample; | 
|  | } | 
|  |  | 
|  | base::TimeDelta FakeIioDevice::GetPeriodForObsoleteSamplesInMilliseconds() { | 
|  | return base::TimeDelta::FromMilliseconds(0.0); | 
|  | } | 
|  |  | 
|  | void FakeIioDevice::DisableFd() { | 
|  | disabled_fd_ = true; | 
|  | if (readable_fd_) | 
|  | CHECK(ReadByte()); | 
|  | } | 
|  |  | 
|  | void FakeIioDevice::AddFailedReadAtKthSample(int k) { | 
|  | CHECK_GE(k, sample_index_); | 
|  |  | 
|  | failed_read_queue_.push(k); | 
|  | } | 
|  |  | 
|  | void FakeIioDevice::SetPauseCallbackAtKthSamples( | 
|  | int k, base::OnceCallback<void()> callback) { | 
|  | CHECK_GE(k, sample_index_); | 
|  | CHECK_LE(k, base::size(kFakeAccelSamples)); | 
|  | CHECK(!pause_index_.has_value());  // pause callback hasn't been set | 
|  |  | 
|  | pause_index_ = k; | 
|  | pause_callback_ = std::move(callback); | 
|  |  | 
|  | if (pause_index_.value() != sample_index_) | 
|  | return; | 
|  |  | 
|  | SetPause(); | 
|  | } | 
|  |  | 
|  | void FakeIioDevice::ResumeReadingSamples() { | 
|  | CHECK(is_paused_); | 
|  |  | 
|  | is_paused_ = false; | 
|  | if (sample_fd_.is_valid() && !readable_fd_) | 
|  | CHECK(WriteByte()); | 
|  | } | 
|  |  | 
|  | bool FakeIioDevice::CreateBuffer() { | 
|  | CHECK(!disabled_fd_); | 
|  |  | 
|  | if (sample_fd_.is_valid()) | 
|  | return true; | 
|  |  | 
|  | int fd = eventfd(0, 0); | 
|  | CHECK_GE(fd, 0); | 
|  | sample_fd_.reset(fd); | 
|  |  | 
|  | if (sample_index_ >= base::size(kFakeAccelSamples) || is_paused_) | 
|  | return true; | 
|  |  | 
|  | if (!WriteByte()) { | 
|  | ClosePipe(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FakeIioDevice::WriteByte() { | 
|  | if (!sample_fd_.is_valid()) | 
|  | return false; | 
|  |  | 
|  | CHECK(!readable_fd_); | 
|  | uint64_t val = 1; | 
|  | CHECK_EQ(write(sample_fd_.get(), &val, sizeof(uint64_t)), sizeof(uint64_t)); | 
|  | readable_fd_ = true; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FakeIioDevice::ReadByte() { | 
|  | if (!sample_fd_.is_valid()) | 
|  | return false; | 
|  |  | 
|  | CHECK(readable_fd_); | 
|  | int64_t val = 1; | 
|  | CHECK_EQ(read(sample_fd_.get(), &val, sizeof(uint64_t)), sizeof(uint64_t)); | 
|  | readable_fd_ = false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void FakeIioDevice::ClosePipe() { | 
|  | sample_fd_.reset(); | 
|  | } | 
|  |  | 
|  | void FakeIioDevice::SetPause() { | 
|  | is_paused_ = true; | 
|  | pause_index_.reset(); | 
|  | std::move(pause_callback_).Run(); | 
|  | if (readable_fd_) | 
|  | CHECK(ReadByte()); | 
|  | } | 
|  |  | 
|  | void FakeIioContext::AddDevice(std::unique_ptr<FakeIioDevice> device) { | 
|  | CHECK(device.get()); | 
|  | devices_.emplace(device->GetId(), std::move(device)); | 
|  | } | 
|  |  | 
|  | void FakeIioContext::AddTrigger(std::unique_ptr<FakeIioDevice> trigger) { | 
|  | CHECK(trigger.get()); | 
|  | triggers_.emplace(trigger->GetId(), std::move(trigger)); | 
|  | } | 
|  |  | 
|  | std::vector<IioDevice*> FakeIioContext::GetDevicesByName( | 
|  | const std::string& name) { | 
|  | return GetFakeByName(name, devices_); | 
|  | } | 
|  |  | 
|  | IioDevice* FakeIioContext::GetDeviceById(int id) { | 
|  | return GetFakeById(id, devices_); | 
|  | } | 
|  |  | 
|  | std::vector<IioDevice*> FakeIioContext::GetAllDevices() { | 
|  | return GetFakeAll(devices_); | 
|  | } | 
|  |  | 
|  | std::vector<IioDevice*> FakeIioContext::GetTriggersByName( | 
|  | const std::string& name) { | 
|  | return GetFakeByName(name, triggers_); | 
|  | } | 
|  |  | 
|  | IioDevice* FakeIioContext::GetTriggerById(int id) { | 
|  | return GetFakeById(id, triggers_); | 
|  | } | 
|  |  | 
|  | std::vector<IioDevice*> FakeIioContext::GetAllTriggers() { | 
|  | return GetFakeAll(triggers_); | 
|  | } | 
|  |  | 
|  | IioDevice* FakeIioContext::GetFakeById( | 
|  | int id, const std::map<int, std::unique_ptr<FakeIioDevice>>& devices_map) { | 
|  | auto k = devices_map.find(id); | 
|  | return (k == devices_map.end()) ? nullptr : k->second.get(); | 
|  | } | 
|  |  | 
|  | std::vector<IioDevice*> FakeIioContext::GetFakeByName( | 
|  | const std::string& name, | 
|  | const std::map<int, std::unique_ptr<FakeIioDevice>>& devices_map) { | 
|  | std::vector<IioDevice*> devices; | 
|  | for (auto const& it : devices_map) { | 
|  | if (name.compare(it.second->GetName()) == 0) | 
|  | devices.push_back(it.second.get()); | 
|  | } | 
|  |  | 
|  | return devices; | 
|  | } | 
|  |  | 
|  | std::vector<IioDevice*> FakeIioContext::GetFakeAll( | 
|  | const std::map<int, std::unique_ptr<FakeIioDevice>>& devices_map) { | 
|  | std::vector<IioDevice*> devices; | 
|  | for (auto const& it : devices_map) | 
|  | devices.push_back(it.second.get()); | 
|  |  | 
|  | return devices; | 
|  | } | 
|  |  | 
|  | }  // namespace fakes | 
|  | }  // namespace libmems |