| // Copyright 2019 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "mems_setup/configuration.h" | |
| #include <algorithm> | |
| #include <initializer_list> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/check.h> | |
| #include <base/files/file_util.h> | |
| #include <base/files/file_path.h> | |
| #include <base/json/json_reader.h> | |
| #include <base/logging.h> | |
| #include <base/process/launch.h> | |
| #include <base/stl_util.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/strings/string_split.h> | |
| #include <base/strings/stringprintf.h> | |
| #include <re2/re2.h> | |
| #include <libmems/common_types.h> | |
| #include <libmems/iio_channel.h> | |
| #include <libmems/iio_context.h> | |
| #include <libmems/iio_device.h> | |
| #include <libmems/iio_device_impl.h> | |
| #include <libsar/sar_config_reader.h> | |
| #include "mems_setup/sensor_location.h" | |
| namespace mems_setup { | |
| namespace { | |
| struct ImuVpdCalibrationEntry { | |
| std::string name; | |
| std::string calib; | |
| std::optional<int> max_value; | |
| std::optional<int> value; | |
| bool missing_is_error; | |
| }; | |
| struct LightVpdCalibrationEntry { | |
| std::string vpd_name; | |
| std::string iio_name; | |
| }; | |
| struct LightColorCalibrationEntry { | |
| std::string iio_name; | |
| std::optional<double> value; | |
| libmems::IioChannel* chn; | |
| }; | |
| #if !USE_IIOSERVICE_PROXIMITY | |
| constexpr char kPowerGroupName[] = "power"; | |
| #endif // !USE_IIOSERVICE_PROXIMITY | |
| constexpr char kIioServiceGroupName[] = "iioservice"; | |
| constexpr char kCalibrationBias[] = "bias"; | |
| constexpr char kCalibrationScale[] = "scale"; | |
| constexpr char kSysfsTriggerPrefix[] = "sysfstrig"; | |
| constexpr int kGyroMaxVpdCalibration = 16384; // 16dps | |
| constexpr int kAccelMaxVpdCalibration = 256; // .250g | |
| constexpr int kAccelSysfsTriggerId = 0; | |
| constexpr int kSysfsTriggerId = -1; | |
| constexpr std::initializer_list<const char*> kAccelAxes = { | |
| "x", | |
| "y", | |
| "z", | |
| }; | |
| constexpr char kTriggerString[] = "trigger"; | |
| constexpr char kDevlinkPrefix[] = "/dev/proximity"; | |
| constexpr char kFilesToSetReadAndOwnership[][28] = { | |
| "buffer/hwfifo_timeout", "buffer/hwfifo_watermark_max", "buffer/enable", | |
| "buffer/length", "trigger/current_trigger"}; | |
| constexpr char kFilesToSetWriteAndOwnership[][24] = {"sampling_frequency", | |
| "buffer/hwfifo_timeout", | |
| "buffer/hwfifo_flush", | |
| "buffer/enable", | |
| "buffer/length", | |
| "trigger/current_trigger", | |
| "flush", | |
| "frequency"}; | |
| constexpr char kScanElementsString[] = "scan_elements"; | |
| constexpr char kEventsString[] = "events"; | |
| } // namespace | |
| const char* Configuration::GetGroupNameForSysfs() { | |
| #if !USE_IIOSERVICE_PROXIMITY | |
| if (kind_ == SensorKind::PROXIMITY) | |
| return kPowerGroupName; | |
| #endif // !USE_IIOSERVICE_PROXIMITY | |
| return kIioServiceGroupName; | |
| } | |
| Configuration::Configuration(libmems::IioContext* context, | |
| libmems::IioDevice* sensor, | |
| Delegate* del) | |
| : delegate_(del), sensor_(sensor), context_(context) { | |
| DCHECK(sensor_); | |
| kind_ = mems_setup::SensorKindFromString( | |
| sensor_->GetName() ? sensor_->GetName() : ""); | |
| } | |
| bool Configuration::Configure() { | |
| iioservice_gid_ = delegate_->FindGroupId(GetGroupNameForSysfs()); | |
| if (!iioservice_gid_.has_value()) { | |
| LOG(ERROR) << "iioservice group not found"; | |
| return false; | |
| } | |
| if (!ConfigureOnKind()) | |
| return false; | |
| if (!SetupPermissions()) | |
| return false; | |
| // If the buffer is enabled, which means mems_setup has already been used on | |
| // this sensor and iioservice is reading the samples from it, skip setting the | |
| // frequency. | |
| if (!sensor_->IsBufferEnabled()) { | |
| sensor_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0); | |
| for (auto& channel : sensor_->GetAllChannels()) | |
| channel->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0); | |
| } | |
| // Ignores the error as it may fail on kernel 4.4 or HID stack sensors. | |
| sensor_->WriteStringAttribute("current_timestamp_clock", "boottime"); | |
| return true; | |
| } | |
| bool Configuration::CopyLightCalibrationFromVpd() { | |
| std::vector<LightVpdCalibrationEntry> calib_attributes = { | |
| {"als_cal_intercept", "calibbias"}, | |
| {"als_cal_slope", "calibscale"}, | |
| }; | |
| auto chn = sensor_->GetChannel("illuminance"); | |
| if (!chn) { | |
| LOG(ERROR) << "No channel illuminance"; | |
| return false; | |
| } | |
| for (auto& calib_attribute : calib_attributes) { | |
| auto attrib_value = delegate_->ReadVpdValue(calib_attribute.vpd_name); | |
| if (!attrib_value.has_value()) { | |
| LOG(ERROR) << "VPD missing calibration value " | |
| << calib_attribute.vpd_name; | |
| continue; | |
| } | |
| double value; | |
| if (!base::StringToDouble(attrib_value.value(), &value)) { | |
| LOG(ERROR) << "VPD calibration value " << calib_attribute.vpd_name | |
| << " has invalid value " << attrib_value.value(); | |
| continue; | |
| } | |
| if (!chn->WriteDoubleAttribute(calib_attribute.iio_name, value)) | |
| LOG(ERROR) << "failed to set calibration value " | |
| << calib_attribute.iio_name; | |
| } | |
| /* | |
| * RGB sensors may need per channel calibration. | |
| */ | |
| std::vector<LightColorCalibrationEntry> calib_color_entries = { | |
| {"illuminance_red", std::nullopt, nullptr}, | |
| {"illuminance_green", std::nullopt, nullptr}, | |
| {"illuminance_blue", std::nullopt, nullptr}, | |
| }; | |
| for (auto& color_entry : calib_color_entries) { | |
| color_entry.chn = sensor_->GetChannel(color_entry.iio_name); | |
| if (!color_entry.chn) | |
| return true; | |
| } | |
| auto attrib_value = delegate_->ReadVpdValue("als_cal_slope_color"); | |
| if (attrib_value.has_value()) { | |
| /* | |
| * Split the attributes in 3 doubles. | |
| */ | |
| std::vector<std::string> attrs = | |
| base::SplitString(attrib_value.value(), " ", base::TRIM_WHITESPACE, | |
| base::SPLIT_WANT_NONEMPTY); | |
| if (attrs.size() == 3) { | |
| for (int i = 0; i < 3; i++) { | |
| double value; | |
| if (!base::StringToDouble(attrs[i], &value)) { | |
| LOG(ERROR) << "VPD_entry " << i << " of als_cal_slope_color " | |
| << "is not a float: " << attrs[i]; | |
| break; | |
| } | |
| calib_color_entries[i].value = value; | |
| } | |
| for (auto& color_entry : calib_color_entries) { | |
| if (!color_entry.value) { | |
| LOG(ERROR) << "No value set for " << color_entry.iio_name; | |
| continue; | |
| } | |
| if (!color_entry.chn->WriteDoubleAttribute("calibscale", | |
| *color_entry.value)) | |
| LOG(WARNING) << "failed to to set calibration value " | |
| << color_entry.iio_name << " to " << *color_entry.value; | |
| } | |
| } else { | |
| LOG(ERROR) << "VPD_entry als_cal_slope_color is malformed : " | |
| << attrib_value.value(); | |
| } | |
| } | |
| return true; | |
| } | |
| bool Configuration::CopyImuCalibationFromVpd(int max_value) { | |
| if (sensor_->IsSingleSensor()) { | |
| auto location = sensor_->GetLocation(); | |
| if (!location || location->empty()) { | |
| LOG(ERROR) << "cannot read a valid sensor location"; | |
| return false; | |
| } | |
| return CopyImuCalibationFromVpd(max_value, location->c_str()); | |
| } else { | |
| bool base_config = CopyImuCalibationFromVpd(max_value, kBaseSensorLocation); | |
| bool lid_config = CopyImuCalibationFromVpd(max_value, kLidSensorLocation); | |
| return base_config && lid_config; | |
| } | |
| } | |
| bool Configuration::CopyImuCalibationFromVpd(int max_value, | |
| const std::string& location) { | |
| const bool is_single_sensor = sensor_->IsSingleSensor(); | |
| std::string kind = SensorKindToString(kind_); | |
| std::vector<ImuVpdCalibrationEntry> calib_attributes = { | |
| {"x", kCalibrationBias, max_value, std::nullopt, true}, | |
| {"y", kCalibrationBias, max_value, std::nullopt, true}, | |
| {"z", kCalibrationBias, max_value, std::nullopt, true}, | |
| {"x", kCalibrationScale, std::nullopt, std::nullopt, false}, | |
| {"y", kCalibrationScale, std::nullopt, std::nullopt, false}, | |
| {"z", kCalibrationScale, std::nullopt, std::nullopt, false}, | |
| }; | |
| for (auto& calib_attribute : calib_attributes) { | |
| auto attrib_name = base::StringPrintf( | |
| "in_%s_%s_%s_calib%s", kind.c_str(), calib_attribute.name.c_str(), | |
| location.c_str(), calib_attribute.calib.c_str()); | |
| auto attrib_value = delegate_->ReadVpdValue(attrib_name.c_str()); | |
| LOG(INFO) << attrib_name | |
| << " attrib_value: " << attrib_value.value_or("nan"); | |
| if (!attrib_value.has_value()) { | |
| if (calib_attribute.missing_is_error) | |
| LOG(ERROR) << "VPD missing calibration value " << attrib_name; | |
| continue; | |
| } | |
| int value; | |
| if (!base::StringToInt(attrib_value.value(), &value)) { | |
| LOG(ERROR) << "VPD calibration value " << attrib_name | |
| << " has invalid value " << attrib_value.value(); | |
| // TODO(crbug/1039454: gwendal): Add uma stats. | |
| continue; | |
| } | |
| if (calib_attribute.max_value && abs(value) > calib_attribute.max_value) { | |
| LOG(ERROR) << "VPD calibration value " << attrib_name | |
| << " has out-of-range value " << attrib_value.value(); | |
| // TODO(crbug/1039454: gwendal): Add uma stats. | |
| return false; | |
| } else { | |
| calib_attribute.value = value; | |
| } | |
| } | |
| for (const auto& calib_attribute : calib_attributes) { | |
| if (!calib_attribute.value) | |
| continue; | |
| auto chn_id = | |
| base::StringPrintf("%s_%s", kind.c_str(), calib_attribute.name.c_str()); | |
| if (!is_single_sensor) | |
| chn_id = base::StringPrintf("%s_%s", chn_id.c_str(), location.c_str()); | |
| auto chn = sensor_->GetChannel(chn_id); | |
| if (!chn) { | |
| LOG(ERROR) << "No channel with id " << chn_id; | |
| return false; | |
| } | |
| auto attrib_name = | |
| base::StringPrintf("calib%s", calib_attribute.calib.c_str()); | |
| if (!chn->WriteNumberAttribute(attrib_name, *calib_attribute.value)) { | |
| LOG(ERROR) << "failed to set calibration value " << attrib_name; | |
| return false; | |
| } | |
| LOG(INFO) << attrib_name << ": " | |
| << chn->ReadNumberAttribute(attrib_name).value_or(-88888); | |
| } | |
| LOG(INFO) << "VPD calibration complete"; | |
| return true; | |
| } | |
| bool Configuration::AddSysfsTrigger(int sysfs_trigger_id) { | |
| std::string dev_name = | |
| libmems::IioDeviceImpl::GetStringFromId(sensor_->GetId()); | |
| // /sys/bus/iio/devices/iio:deviceX | |
| base::FilePath sys_dev_path = sensor_->GetPath(); | |
| if (!delegate_->Exists(sys_dev_path.Append(kTriggerString))) { | |
| // Uses FIFO and doesn't need a trigger. | |
| return true; | |
| } | |
| // There is a potential cross-process race here, where multiple instances | |
| // of this tool may be trying to access the trigger at once. To solve this, | |
| // first see if the trigger is already there. If not, try to create it, and | |
| // then try to access it again. Only if the latter access fails then | |
| // error out. | |
| auto trigger_name = | |
| base::StringPrintf("%s%d", kSysfsTriggerPrefix, sysfs_trigger_id); | |
| auto triggers = context_->GetTriggersByName(trigger_name); | |
| if (triggers.size() > 1) { | |
| LOG(ERROR) << "Several triggers with the same name " << trigger_name | |
| << " is not expected."; | |
| return false; | |
| } | |
| if (triggers.size() == 0) { | |
| LOG(INFO) << "trigger " << trigger_name << " not found; adding"; | |
| auto iio_sysfs_trigger = context_->GetTriggerById(kSysfsTriggerId); | |
| if (iio_sysfs_trigger == nullptr) { | |
| LOG(ERROR) << "cannot find iio_trig_sysfs kernel module"; | |
| return false; | |
| } | |
| if (!iio_sysfs_trigger->WriteNumberAttribute("add_trigger", | |
| sysfs_trigger_id)) { | |
| // It may happen if another instance of mems_setup is running in parallel. | |
| LOG(WARNING) << "cannot instantiate trigger " << trigger_name; | |
| } | |
| context_->Reload(); | |
| triggers = context_->GetTriggersByName(trigger_name); | |
| if (triggers.size() != 1) { | |
| LOG(ERROR) << "Trigger " << trigger_name << " not been created properly"; | |
| return false; | |
| } | |
| } | |
| if (!sensor_->SetTrigger(triggers[0])) { | |
| LOG(ERROR) << "cannot set sensor's trigger to " << trigger_name; | |
| return false; | |
| } | |
| base::FilePath trigger_now = triggers[0]->GetPath().Append("trigger_now"); | |
| std::optional<gid_t> chronos_gid = delegate_->FindGroupId("chronos"); | |
| if (!chronos_gid) { | |
| LOG(ERROR) << "chronos group not found"; | |
| return false; | |
| } | |
| if (!delegate_->SetOwnership(trigger_now, -1, chronos_gid.value())) { | |
| LOG(ERROR) << "cannot configure ownership on the trigger"; | |
| return false; | |
| } | |
| int permission = delegate_->GetPermissions(trigger_now); | |
| permission |= base::FILE_PERMISSION_WRITE_BY_GROUP; | |
| if (!delegate_->SetPermissions(trigger_now, permission)) { | |
| LOG(ERROR) << "cannot configure permissions on the trigger"; | |
| return false; | |
| } | |
| LOG(INFO) << "sysfs trigger setup complete"; | |
| return true; | |
| } | |
| bool Configuration::EnableAccelScanElements() { | |
| auto timestamp = sensor_->GetChannel("timestamp"); | |
| if (!timestamp) { | |
| LOG(ERROR) << "cannot find timestamp channel"; | |
| return false; | |
| } | |
| if (!timestamp->SetScanElementsEnabled(false)) { | |
| LOG(ERROR) << "failed to disable timestamp channel"; | |
| return false; | |
| } | |
| std::vector<std::string> channels_to_enable; | |
| if (sensor_->IsSingleSensor()) { | |
| for (const auto& axis : kAccelAxes) { | |
| channels_to_enable.push_back(base::StringPrintf("accel_%s", axis)); | |
| } | |
| } else { | |
| for (const auto& axis : kAccelAxes) { | |
| channels_to_enable.push_back( | |
| base::StringPrintf("accel_%s_%s", axis, kBaseSensorLocation)); | |
| channels_to_enable.push_back( | |
| base::StringPrintf("accel_%s_%s", axis, kLidSensorLocation)); | |
| } | |
| } | |
| for (const auto& chan_name : channels_to_enable) { | |
| auto channel = sensor_->GetChannel(chan_name); | |
| if (!channel) { | |
| LOG(ERROR) << "cannot find channel " << chan_name; | |
| return false; | |
| } | |
| if (!channel->SetScanElementsEnabled(true)) { | |
| LOG(ERROR) << "failed to enable channel " << chan_name; | |
| return false; | |
| } | |
| } | |
| sensor_->EnableBuffer(1); | |
| if (!sensor_->IsBufferEnabled()) { | |
| LOG(ERROR) << "failed to enable buffer"; | |
| return false; | |
| } | |
| LOG(INFO) << "buffer enabled"; | |
| return true; | |
| } | |
| bool Configuration::EnableCalibration(bool enable) { | |
| auto calibration = sensor_->GetChannel("calibration"); | |
| if (!calibration) { | |
| LOG(ERROR) << "cannot find calibration channel"; | |
| return false; | |
| } | |
| return calibration->SetScanElementsEnabled(enable); | |
| } | |
| bool Configuration::EnableKeyboardAngle() { | |
| base::FilePath kb_wake_angle = | |
| base::FilePath("/sys/class/chromeos/cros_ec/kb_wake_angle"); | |
| if (!delegate_->Exists(kb_wake_angle)) { | |
| LOG(INFO) << kb_wake_angle.value() | |
| << " not found; will not enable EC wake angle"; | |
| return true; | |
| } | |
| std::optional<gid_t> power_gid = delegate_->FindGroupId("power"); | |
| if (!power_gid) { | |
| LOG(ERROR) << "cannot configure ownership on the wake angle file"; | |
| return false; | |
| } | |
| delegate_->SetOwnership(kb_wake_angle, -1, power_gid.value()); | |
| int permission = delegate_->GetPermissions(kb_wake_angle); | |
| permission |= base::FILE_PERMISSION_WRITE_BY_GROUP; | |
| delegate_->SetPermissions(kb_wake_angle, permission); | |
| LOG(INFO) << "keyboard angle enabled"; | |
| return true; | |
| } | |
| bool Configuration::ConfigureOnKind() { | |
| switch (kind_) { | |
| case SensorKind::ACCELEROMETER: | |
| return ConfigAccelerometer(); | |
| case SensorKind::GYROSCOPE: | |
| return ConfigGyro(); | |
| case SensorKind::LIGHT: | |
| return ConfigIlluminance(); | |
| case SensorKind::SYNC: | |
| // No other configs needed. | |
| return true; | |
| case SensorKind::MAGNETOMETER: | |
| // No other configs needed. | |
| return true; | |
| case SensorKind::LID_ANGLE: | |
| // No other configs needed. | |
| return true; | |
| case SensorKind::PROXIMITY: | |
| return ConfigProximity(); | |
| case SensorKind::BAROMETER: | |
| // TODO(chenghaoyang): Setup calibrations for the barometer. | |
| return true; | |
| case SensorKind::HID_OTHERS: | |
| // No other configs needed. | |
| return true; | |
| default: | |
| CHECK(kind_ == SensorKind::OTHERS); | |
| LOG(ERROR) << sensor_->GetName() << " unimplemented"; | |
| return false; | |
| } | |
| } | |
| bool Configuration::ConfigGyro() { | |
| CopyImuCalibationFromVpd(kGyroMaxVpdCalibration); | |
| LOG(INFO) << "gyroscope configuration complete"; | |
| return true; | |
| } | |
| bool Configuration::ConfigAccelerometer() { | |
| CopyImuCalibationFromVpd(kAccelMaxVpdCalibration); | |
| if (!AddSysfsTrigger(kAccelSysfsTriggerId)) | |
| return false; | |
| if (!EnableKeyboardAngle()) | |
| return false; | |
| /* | |
| * Gather gyroscope. If one of them is on the same plane, set | |
| * accelerometer range to 4g to meet Android 10 CCD Requirements | |
| * (Section 7.1.4, C.1.4). | |
| * If no gyro found, set range to 4g on the lid accel if there are 2 accels | |
| */ | |
| int range = 0; | |
| auto location = sensor_->GetLocation(); | |
| if (location && !location->empty()) { | |
| auto gyros = context_->GetDevicesByName("cros-ec-gyro"); | |
| if (gyros.size() > 1) { | |
| range = 4; | |
| } else if (gyros.size() == 1) { | |
| if (strcmp(location->c_str(), gyros[0]->GetLocation()->c_str()) == 0) | |
| range = 4; | |
| else | |
| range = 2; | |
| } else { | |
| auto accels = context_->GetDevicesByName("cros-ec-accel"); | |
| if (accels.size() == 1) | |
| range = 4; | |
| else if (accels.size() > 1 && | |
| strcmp(location->c_str(), kLidSensorLocation) == 0) | |
| range = 4; | |
| else | |
| range = 2; | |
| } | |
| if (!sensor_->WriteNumberAttribute(kCalibrationScale, range)) | |
| return false; | |
| } | |
| LOG(INFO) << "accelerometer configuration complete"; | |
| return true; | |
| } | |
| bool Configuration::ConfigIlluminance() { | |
| if (strcmp(sensor_->GetName(), "acpi-als") == 0) { | |
| std::string trigger_name = | |
| base::StringPrintf(libmems::kHrtimerNameFormatString, sensor_->GetId()); | |
| if (context_->GetTriggersByName(trigger_name).empty()) { | |
| base::FilePath hrtimer_path("/sys/kernel/config/iio/triggers/hrtimer"); | |
| hrtimer_path = hrtimer_path.Append(trigger_name); | |
| if (!delegate_->Exists(hrtimer_path) && | |
| !delegate_->ProbeKernelModule("iio-trig-hrtimer")) { | |
| LOG(ERROR) << "cannot load iio-trig-hrtimer module"; | |
| return false; | |
| } | |
| if (!delegate_->CreateDirectory(hrtimer_path)) { | |
| LOG(ERROR) << "cannot mkdir " << hrtimer_path.value() | |
| << " to create the hrtimer device"; | |
| return false; | |
| } | |
| } | |
| context_->Reload(); | |
| auto triggers = context_->GetTriggersByName(trigger_name); | |
| if (triggers.empty()) { | |
| LOG(ERROR) << "cannot find acpi-als's trigger"; | |
| return false; | |
| } | |
| // Don't set |trigger| as |sensor_|'s trigger, or else the samples start | |
| // flowing. | |
| auto trigger = triggers.front(); | |
| // /sys/bus/iio/devices/triggerX | |
| base::FilePath sys_trg_path = | |
| trigger->GetPath().Append(libmems::kSamplingFrequencyAttr); | |
| SetReadPermissionAndOwnership(sys_trg_path); | |
| SetWritePermissionAndOwnership(sys_trg_path); | |
| } | |
| if (!CopyLightCalibrationFromVpd()) | |
| return false; | |
| // Disable calibration: it can fail if the light sensor does not support | |
| // calibration mode. | |
| EnableCalibration(false); | |
| LOG(INFO) << "light configuration complete"; | |
| return true; | |
| } | |
| bool Configuration::ConfigProximity() { | |
| auto* cros_config = delegate_->GetCrosConfig(); | |
| auto sys_path = sensor_->GetAbsoluteSysPath(); | |
| if (!sys_path.has_value()) { | |
| LOG(ERROR) << "Invalid absolute SysPath"; | |
| return false; | |
| } | |
| bool isSar; | |
| auto devlink_opt = delegate_->GetIioSarSensorDevlink(sys_path->value()); | |
| if (devlink_opt.has_value()) | |
| isSar = true; | |
| else if (IsIioActivitySensor(sys_path->value())) | |
| isSar = false; | |
| else | |
| return false; | |
| if (isSar) { | |
| // |devlink_opt.value()| should have prefix "/dev/proximity_" or | |
| // "/dev/proximity-". | |
| if (devlink_opt.value().compare(0, std::strlen(kDevlinkPrefix), | |
| kDevlinkPrefix) != 0) { | |
| LOG(ERROR) << "Devlink isn't in the proper format: " | |
| << devlink_opt.value(); | |
| return false; | |
| } | |
| auto sar_config_reader = | |
| libsar::SarConfigReader(cros_config, devlink_opt.value(), | |
| delegate_->GetSarConfigReaderDelegate()); | |
| if (!sar_config_reader.isCellular() && !sar_config_reader.isWifi()) { | |
| LOG(ERROR) << "Invalid devlink: " << devlink_opt.value() | |
| << ", neither lte nor wifi"; | |
| return false; | |
| } | |
| auto config_dict_opt = sar_config_reader.GetSarConfigDict(); | |
| if (!config_dict_opt.has_value()) | |
| return false; | |
| const base::Value::Dict& config_dict = config_dict_opt.value(); | |
| std::optional<double> sampling_frequency = | |
| config_dict.FindDouble("samplingFrequency"); | |
| if (sampling_frequency.has_value()) { | |
| if (!sensor_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, | |
| sampling_frequency.value())) { | |
| LOG(ERROR) << "Could not set proximity sensor sampling frequency"; | |
| return false; | |
| } | |
| } | |
| const base::Value::List* channel_list = | |
| config_dict.FindList("channelConfig"); | |
| if (channel_list) { | |
| // Semtech supports multiple channels, a given observer may received | |
| // FAR/NEAR message from multiple channels. | |
| for (const base::Value& channel : *channel_list) { | |
| const base::Value::Dict& channel_dict = channel.GetDict(); | |
| const std::string* channel_name = channel_dict.FindString("channel"); | |
| if (!channel_name) { | |
| LOG(ERROR) << "channel identifier required"; | |
| return false; | |
| } | |
| int channel_int; | |
| if (!base::StringToInt(*channel_name, &channel_int)) { | |
| LOG(ERROR) << "Invalid channel_name: " << channel_name; | |
| return false; | |
| } | |
| std::optional<int> hardwaregain = channel_dict.FindInt("hardwaregain"); | |
| if (hardwaregain.has_value()) { | |
| auto* iio_channel = sensor_->GetChannel("proximity" + *channel_name); | |
| if (!iio_channel || !iio_channel->WriteNumberAttribute( | |
| "hardwaregain", hardwaregain.value())) { | |
| LOG(ERROR) << "Could not set proximity sensor hardware gain"; | |
| return false; | |
| } | |
| } | |
| if (!SetIioRisingFallingValue( | |
| channel_dict, "", "events/in_proximity" + *channel_name + "_", | |
| "_value")) { | |
| return false; | |
| } | |
| if (!SetIioRisingFallingValue( | |
| channel_dict, "Hysteresis", | |
| "events/in_proximity" + *channel_name + "_", "_hysteresis")) { | |
| return false; | |
| } | |
| } | |
| } | |
| if (!SetIioRisingFallingValue(config_dict, "Period", "events/", "_period")) | |
| return false; | |
| } | |
| LOG(INFO) << "proximity configuration complete"; | |
| return true; | |
| } | |
| bool Configuration::IsIioActivitySensor(const std::string& sys_path) { | |
| return sys_path.find("-activity") != std::string::npos; | |
| } | |
| bool Configuration::SetIioRisingFallingValue( | |
| const base::Value::Dict& config_dict, | |
| const std::string& config_postfix, | |
| const std::string& path_prefix, | |
| const std::string& postfix) { | |
| std::string rising_config = "threshRising" + config_postfix; | |
| std::string falling_config = "threshFalling" + config_postfix; | |
| std::optional<int> rising_value = config_dict.FindInt(rising_config); | |
| std::optional<int> falling_value = config_dict.FindInt(falling_config); | |
| if (!rising_value.has_value() && !falling_value.has_value()) | |
| return true; | |
| bool try_either = rising_value.has_value() && falling_value.has_value() && | |
| falling_value.value() == rising_value.value(); | |
| std::string prefix = path_prefix + "thresh_"; | |
| std::string falling_path = prefix + "falling" + postfix; | |
| std::string rising_path = prefix + "rising" + postfix; | |
| std::string either_path = prefix + "either" + postfix; | |
| if (!try_either || | |
| !sensor_->WriteNumberAttribute(either_path, rising_value.value())) { | |
| if (rising_value.has_value() && | |
| !sensor_->WriteNumberAttribute(rising_path, rising_value.value())) { | |
| LOG(ERROR) << "Could not set proximity sensor " << rising_path << " to " | |
| << rising_value.value(); | |
| return false; | |
| } | |
| if (falling_value.has_value() && | |
| !sensor_->WriteNumberAttribute(falling_path, falling_value.value())) { | |
| LOG(ERROR) << "Could not set proximity sensor " << falling_path << " to " | |
| << falling_value.value(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool Configuration::SetupPermissions() { | |
| std::vector<base::FilePath> files_to_set_read_own; | |
| std::vector<base::FilePath> files_to_set_write_own; | |
| std::string dev_name = | |
| libmems::IioDeviceImpl::GetStringFromId(sensor_->GetId()); | |
| // /dev/iio:deviceX | |
| base::FilePath dev_path = | |
| base::FilePath(libmems::kDevString).Append(dev_name.c_str()); | |
| if (!delegate_->Exists(dev_path)) { | |
| LOG(ERROR) << "Missing path: " << dev_path.value(); | |
| return false; | |
| } | |
| files_to_set_read_own.push_back(dev_path); | |
| files_to_set_write_own.push_back(dev_path); | |
| // /sys/bus/iio/devices/iio:deviceX | |
| base::FilePath sys_dev_path = sensor_->GetPath(); | |
| // Files under /sys/bus/iio/devices/iio:deviceX/. | |
| auto files = delegate_->EnumerateAllFiles(sys_dev_path); | |
| files_to_set_read_own.insert(files_to_set_read_own.end(), files.begin(), | |
| files.end()); | |
| for (const base::FilePath& file : files) { | |
| std::string name = file.BaseName().value(); | |
| if (RE2::FullMatch(name, "in_.*_sampling_frequency")) | |
| files_to_set_write_own.push_back(file); | |
| } | |
| // Files under /sys/bus/iio/devices/iio:deviceX/scan_elements/. | |
| files = | |
| delegate_->EnumerateAllFiles(sys_dev_path.Append(kScanElementsString)); | |
| files_to_set_read_own.insert(files_to_set_read_own.end(), files.begin(), | |
| files.end()); | |
| for (const base::FilePath& file : files) { | |
| std::string name = file.BaseName().value(); | |
| if (RE2::FullMatch(name, "in_.*_en")) | |
| files_to_set_write_own.push_back(file); | |
| } | |
| // Files under /sys/bus/iio/devices/iio:deviceX/events/. | |
| files = delegate_->EnumerateAllFiles(sys_dev_path.Append(kEventsString)); | |
| files_to_set_read_own.insert(files_to_set_read_own.end(), files.begin(), | |
| files.end()); | |
| for (const base::FilePath& file : files) { | |
| std::string name = file.BaseName().value(); | |
| if (RE2::FullMatch(name, "in_.*_en")) | |
| files_to_set_write_own.push_back(file); | |
| } | |
| for (auto file : kFilesToSetReadAndOwnership) | |
| files_to_set_read_own.push_back(sys_dev_path.Append(file)); | |
| for (auto file : kFilesToSetWriteAndOwnership) | |
| files_to_set_write_own.push_back(sys_dev_path.Append(file)); | |
| // Set permissions and ownerships. | |
| bool result = true; | |
| for (base::FilePath path : files_to_set_read_own) | |
| result &= SetReadPermissionAndOwnership(path); | |
| for (base::FilePath path : files_to_set_write_own) | |
| result &= SetWritePermissionAndOwnership(path); | |
| return result; | |
| } | |
| bool Configuration::SetReadPermissionAndOwnership(base::FilePath file_path) { | |
| DCHECK(iioservice_gid_.has_value()); | |
| if (!delegate_->Exists(file_path)) | |
| return true; | |
| bool result = true; | |
| int permission = delegate_->GetPermissions(file_path); | |
| permission |= base::FILE_PERMISSION_READ_BY_GROUP; | |
| if (!delegate_->SetPermissions(file_path, permission)) { | |
| LOG(ERROR) << "cannot configure permissions on " << file_path.value(); | |
| result = false; | |
| } | |
| if (!delegate_->SetOwnership(file_path, -1, iioservice_gid_.value())) { | |
| LOG(ERROR) << "cannot configure ownership on " << file_path.value(); | |
| result = false; | |
| } | |
| return result; | |
| } | |
| bool Configuration::SetWritePermissionAndOwnership(base::FilePath file_path) { | |
| DCHECK(iioservice_gid_.has_value()); | |
| if (!delegate_->Exists(file_path)) | |
| return true; | |
| bool result = true; | |
| int permission = delegate_->GetPermissions(file_path); | |
| permission |= base::FILE_PERMISSION_WRITE_BY_GROUP; | |
| if (!delegate_->SetPermissions(file_path, permission)) { | |
| LOG(ERROR) << "cannot configure permissions on " << file_path.value(); | |
| result = false; | |
| } | |
| if (!delegate_->SetOwnership(file_path, -1, iioservice_gid_.value())) { | |
| LOG(ERROR) << "cannot configure ownership on " << file_path.value(); | |
| result = false; | |
| } | |
| return result; | |
| } | |
| } // namespace mems_setup |