| /* |
| * Copyright 2020 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 "cros-camera/device_config.h" |
| |
| #include <sys/ioctl.h> |
| |
| #include <algorithm> |
| #include <optional> |
| |
| #include <base/containers/span.h> |
| #include <base/files/file_util.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/system/sys_info.h> |
| #include <chromeos-config/libcros_config/cros_config.h> |
| |
| #include "base/containers/contains.h" |
| #include "cros-camera/common.h" |
| |
| namespace cros { |
| |
| namespace { |
| |
| uint16_t const kCrc16CcittFalseTable[256] = { |
| 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, |
| 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, |
| 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, |
| 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, |
| 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, |
| 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, |
| 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, |
| 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, |
| 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, |
| 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, |
| 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, |
| 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, |
| 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, |
| 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, |
| 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, |
| 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, |
| 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, |
| 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, |
| 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, |
| 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, |
| 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, |
| 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, |
| 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, |
| 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, |
| 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, |
| 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, |
| 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, |
| 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, |
| 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, |
| }; |
| |
| uint16_t Crc16CcittFalse(base::span<const uint8_t> buf, uint16_t init) { |
| uint16_t crc = init; |
| for (uint8_t b : buf) { |
| crc = (crc << 8) ^ kCrc16CcittFalseTable[(crc >> 8) ^ b]; |
| } |
| return crc; |
| } |
| |
| constexpr char kCrosConfigCameraPath[] = "/camera"; |
| constexpr char kCrosConfigLegacyUsbKey[] = "legacy-usb"; |
| |
| constexpr char kSysfsV4lClassRoot[] = "/sys/class/video4linux"; |
| constexpr char kSysfsNvmemDevicesRoot[] = "/sys/bus/nvmem/devices"; |
| constexpr char kSysfsI2cDevicesRoot[] = "/sys/bus/i2c/devices"; |
| constexpr char kVendorIdPath[] = "device/vendor_id"; |
| constexpr size_t kEepromIdBlockAlignment = 32u; |
| |
| bool ValidateCameraModuleInfo(base::span<const uint8_t> section) { |
| if (section.size() < sizeof(EepromIdBlock)) { |
| return false; |
| } |
| auto* info = reinterpret_cast<const EepromIdBlock*>(section.data()); |
| const uint16_t crc = |
| Crc16CcittFalse(section.subspan(offsetof(EepromIdBlock, version)), 0u); |
| return strncmp(info->os, "CrOS", 4) == 0 && info->crc == crc && |
| info->version == 1u; |
| } |
| |
| std::optional<EepromIdBlock> FindCameraEepromIdBlock(const std::string& mem) { |
| static_assert(sizeof(EepromIdBlock) <= kEepromIdBlockAlignment); |
| const size_t alignment = kEepromIdBlockAlignment; |
| const uint8_t* data_end = |
| reinterpret_cast<const uint8_t*>(mem.data()) + mem.size(); |
| for (size_t offset_from_end = alignment + mem.size() % alignment; |
| offset_from_end <= mem.size(); offset_from_end += alignment) { |
| base::span<const uint8_t> section = |
| base::make_span(data_end - offset_from_end, sizeof(EepromIdBlock)); |
| if (ValidateCameraModuleInfo(section)) { |
| return *reinterpret_cast<const EepromIdBlock*>(section.data()); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| } // namespace |
| |
| std::optional<DeviceConfig> DeviceConfig::Create() { |
| DeviceConfig res = {}; |
| |
| if (!PopulateCrosConfigCameraInfo(&res)) { |
| return std::nullopt; |
| } |
| |
| return std::make_optional<DeviceConfig>(res); |
| } |
| |
| std::optional<int> DeviceConfig::GetCameraCount(Interface interface) const { |
| if (!count_.has_value()) |
| return std::nullopt; |
| // |count_| includes both MIPI and USB cameras. If |count_| is not 0, we need |
| // the |cros_config_camera_devices_| information to determine the numbers. |
| if (*count_ == 0) |
| return 0; |
| if (cros_config_cameras_.empty()) |
| return std::nullopt; |
| return std::count_if( |
| cros_config_cameras_.begin(), cros_config_cameras_.end(), |
| [=](const CrosConfigCameraInfo& d) { return d.interface == interface; }); |
| } |
| |
| std::optional<int> DeviceConfig::GetOrientationFromFacing( |
| LensFacing facing) const { |
| auto iter = std::find_if( |
| cros_config_cameras_.begin(), cros_config_cameras_.end(), |
| [=](const CrosConfigCameraInfo& d) { return d.facing == facing; }); |
| if (iter == cros_config_cameras_.end()) |
| return std::nullopt; |
| return iter->orientation; |
| } |
| |
| base::span<const PlatformCameraInfo> DeviceConfig::GetPlatformCameraInfo() { |
| if (!platform_cameras_.has_value()) { |
| platform_cameras_.emplace(); |
| PopulatePlatformCameraInfo(); |
| } |
| return *platform_cameras_; |
| } |
| |
| void DeviceConfig::ProbeSensorSubdev(struct media_entity_desc* desc, |
| const base::FilePath& path) { |
| V4L2SensorInfo sensor{.name = desc->name}; |
| std::string vendor_id; |
| const base::FilePath& vendor_id_path = path.Append(kVendorIdPath); |
| if (base::ReadFileToStringWithMaxSize(vendor_id_path, &vendor_id, 64)) { |
| base::TrimWhitespaceASCII(vendor_id, base::TRIM_ALL, &sensor.vendor_id); |
| } |
| sensor.subdev_path = base::MakeAbsoluteFilePath(path); |
| LOG(INFO) << "Found V4L2 sensor subdev on " << sensor.subdev_path; |
| |
| v4l2_sensors_.emplace_back(std::move(sensor)); |
| } |
| |
| base::FilePath DeviceConfig::FindSubdevSysfsByDevId(int major, int minor) { |
| base::FileEnumerator dev_enum(base::FilePath(kSysfsV4lClassRoot), false, |
| base::FileEnumerator::DIRECTORIES, |
| "v4l-subdev*"); |
| for (base::FilePath name = dev_enum.Next(); !name.empty(); |
| name = dev_enum.Next()) { |
| base::FilePath dev_path = name.Append("dev"); |
| std::string dev_id("255:255"); |
| if (!base::ReadFileToStringWithMaxSize(dev_path, &dev_id, dev_id.size())) { |
| LOG(ERROR) << "Failed to read device ID of '" << dev_path.value() |
| << "' from sysfs"; |
| continue; |
| } |
| base::TrimWhitespaceASCII(dev_id, base::TRIM_ALL, &dev_id); |
| |
| std::ostringstream stream; |
| stream << major << ":" << minor; |
| if (dev_id == stream.str()) |
| return name; |
| } |
| |
| return base::FilePath(); |
| } |
| |
| void DeviceConfig::ProbeMediaController(int media_fd) { |
| struct media_entity_desc desc; |
| |
| for (desc.id = MEDIA_ENT_ID_FLAG_NEXT; |
| !ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &desc); |
| desc.id |= MEDIA_ENT_ID_FLAG_NEXT) { |
| if (desc.type != MEDIA_ENT_T_V4L2_SUBDEV_SENSOR) |
| continue; |
| |
| const base::FilePath& path = |
| FindSubdevSysfsByDevId(desc.dev.major, desc.dev.minor); |
| if (path.empty()) { |
| LOG(ERROR) << "v4l-subdev node for sensor '" << desc.name |
| << "' not found"; |
| continue; |
| } |
| |
| LOG(INFO) << "Probing sensor '" << desc.name << "' (" |
| << path.BaseName().value() << ")"; |
| ProbeSensorSubdev(&desc, path); |
| } |
| } |
| |
| void DeviceConfig::AddV4L2Sensors() { |
| base::FileEnumerator dev_enum(base::FilePath("/dev"), false, |
| base::FileEnumerator::FILES, "media*"); |
| for (base::FilePath name = dev_enum.Next(); !name.empty(); |
| name = dev_enum.Next()) { |
| auto fd = base::ScopedFD(open(name.value().c_str(), O_RDWR)); |
| if (!fd.is_valid()) { |
| LOG(ERROR) << "Failed to open '" << name.value() << "'"; |
| continue; |
| } |
| |
| media_device_info info = {}; |
| if (ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info) != 0) { |
| PLOG(ERROR) << "Failed to get media device info on " << name; |
| continue; |
| } |
| if (strcmp(info.driver, "uvcvideo") == 0) { |
| continue; |
| } |
| |
| LOG(INFO) << "Probing media device '" << name.value() << "'"; |
| ProbeMediaController(fd.get()); |
| } |
| } |
| |
| void DeviceConfig::AddCameraEeproms() { |
| auto read_eeprom = |
| [&](base::FilePath from_path) -> std::optional<EepromIdBlock> { |
| std::string content; |
| if (!base::ReadFileToString(from_path, &content)) { |
| return std::nullopt; |
| } |
| std::optional<EepromIdBlock> id_block = FindCameraEepromIdBlock(content); |
| if (!id_block.has_value()) { |
| // Not a camera EEPROM. Ignore the device. |
| return std::nullopt; |
| } |
| LOG(INFO) << "Read camera eeprom from " << from_path; |
| return id_block; |
| }; |
| |
| // Try finding the EEPROM file corresponding to the given |nvmem_path| by |
| // matching the devname. |
| auto locate_eeprom_file = |
| [](base::FilePath nvmem_path) -> std::optional<base::FilePath> { |
| // sysfs device name is of the form "<major devname>:<minor devname>". We |
| // only want to match the major devname because the minor devname can be |
| // different on the nvmem and i2c buses. |
| auto get_major_name = [](std::string bus_device_name) -> std::string { |
| return base::SplitString(bus_device_name, ":", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)[0]; |
| }; |
| std::string devname = get_major_name(nvmem_path.BaseName().value()); |
| base::FileEnumerator dev_enum(base::FilePath(kSysfsI2cDevicesRoot), false, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath dev_path = dev_enum.Next(); !dev_path.empty(); |
| dev_path = dev_enum.Next()) { |
| if (get_major_name(dev_path.BaseName().value()) == devname) { |
| base::FilePath eeprom_path = dev_path.Append("eeprom"); |
| if (base::PathExists(eeprom_path)) { |
| return eeprom_path; |
| } |
| } |
| } |
| return std::nullopt; |
| }; |
| |
| base::FileEnumerator dev_enum(base::FilePath(kSysfsNvmemDevicesRoot), false, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath dev_path = dev_enum.Next(); !dev_path.empty(); |
| dev_path = dev_enum.Next()) { |
| // Some Thunderbolt nvmem devices can take multiple minutes to be read. |
| // Avoid reading them, as the camera eeprom will not be sitting there |
| // anyway (b/213525227). |
| if (dev_path.BaseName().value().find("nvm_active") == 0) { |
| LOGF(INFO) << "Ignoring nvmem at " << dev_path; |
| continue; |
| } |
| const base::FilePath nvmem_path = |
| base::MakeAbsoluteFilePath(dev_path.Append("nvmem")); |
| if (nvmem_path.empty()) { |
| LOG(ERROR) << "Failed to resolve absolute nvmem path from " << dev_path; |
| continue; |
| } |
| std::optional<EepromIdBlock> id_block = read_eeprom(nvmem_path); |
| if (!id_block.has_value()) { |
| // User 'arc-camera' does not have the permission to read the EEPROM file |
| // on the nvmem bus (/sys/bus/nvmem/devices/*/nvmem). Fallback to reading |
| // the EEPROM file on the i2c bus (/sys/bus/i2c/devices/*/eeprom). |
| std::optional<base::FilePath> eeprom_path = locate_eeprom_file(dev_path); |
| if (eeprom_path.has_value()) { |
| id_block = read_eeprom(eeprom_path.value()); |
| } |
| } |
| if (!id_block.has_value()) |
| continue; |
| eeproms_.push_back(EepromInfo{ |
| .id_block = *id_block, |
| .nvmem_path = std::move(nvmem_path), |
| }); |
| } |
| } |
| |
| void DeviceConfig::PopulatePlatformCameraInfo() { |
| AddCameraEeproms(); |
| AddV4L2Sensors(); |
| |
| // Associate probed nvmems and v4l-subdevs by their absolute sysfs device |
| // paths. When both devices exist, they are expected to locate on the same |
| // I2C bus. For example: |
| // /path/to/i2c/sysfs - i2c-2 - 2-0010 - video4linux - v4l-subdev6 |
| // \- 2-0058 - 2-00580 - nvmem |
| std::set<const V4L2SensorInfo*> associated_sensors; |
| for (const EepromInfo& eeprom : eeproms_) { |
| std::vector<std::string> path; |
| eeprom.nvmem_path.GetComponents(&path); |
| CHECK_GE(path.size(), 4u); |
| auto iter = std::find_if(v4l2_sensors_.begin(), v4l2_sensors_.end(), |
| [&](const V4L2SensorInfo& sensor) { |
| std::vector<std::string> p; |
| sensor.subdev_path.GetComponents(&p); |
| return std::equal(path.begin(), path.end() - 3, |
| p.begin()); |
| }); |
| auto info = PlatformCameraInfo{ |
| .eeprom = eeprom, |
| .sysfs_name = path[path.size() - 4] + '/' + path[path.size() - 3], |
| }; |
| if (iter != v4l2_sensors_.end()) { |
| info.v4l2_sensor = *iter; |
| associated_sensors.insert(&*iter); |
| } |
| platform_cameras_->push_back(std::move(info)); |
| } |
| for (const V4L2SensorInfo& sensor : v4l2_sensors_) { |
| if (!base::Contains(associated_sensors, &sensor)) { |
| platform_cameras_->push_back(PlatformCameraInfo{ |
| .v4l2_sensor = sensor, |
| }); |
| } |
| } |
| } |
| |
| // static |
| bool DeviceConfig::PopulateCrosConfigCameraInfo(DeviceConfig* dev_conf) { |
| CHECK(dev_conf); |
| brillo::CrosConfig cros_config; |
| |
| if (!cros_config.Init()) { |
| LOGF(ERROR) << "Failed to initialize CrOS config"; |
| return false; |
| } |
| |
| if (!cros_config.GetString("/", "name", &dev_conf->model_name_)) { |
| LOGF(ERROR) << "Failed to get model name of CrOS device"; |
| return false; |
| } |
| |
| std::string use_legacy_usb; |
| if (cros_config.GetString(kCrosConfigCameraPath, kCrosConfigLegacyUsbKey, |
| &use_legacy_usb)) { |
| if (use_legacy_usb == "true") { |
| LOGF(INFO) << "The CrOS device is marked to have v1 camera devices"; |
| } |
| dev_conf->is_v1_device_ = use_legacy_usb == "true"; |
| } else { |
| dev_conf->is_v1_device_ = false; |
| } |
| |
| std::string count_str; |
| if (cros_config.GetString("/camera", "count", &count_str)) { |
| dev_conf->count_ = std::stoi(count_str); |
| } |
| |
| for (int i = 0;; ++i) { |
| std::string interface; |
| if (!cros_config.GetString(base::StringPrintf("/camera/devices/%i", i), |
| "interface", &interface)) { |
| break; |
| } |
| std::string facing, orientation; |
| CHECK(cros_config.GetString(base::StringPrintf("/camera/devices/%i", i), |
| "facing", &facing)); |
| CHECK(cros_config.GetString(base::StringPrintf("/camera/devices/%i", i), |
| "orientation", &orientation)); |
| dev_conf->cros_config_cameras_.push_back(CrosConfigCameraInfo{ |
| .interface = interface == "usb" ? Interface::kUsb : Interface::kMipi, |
| .facing = facing == "front" ? LensFacing::kFront : LensFacing::kBack, |
| .orientation = std::stoi(orientation), |
| }); |
| } |
| if (!dev_conf->cros_config_cameras_.empty()) { |
| CHECK(dev_conf->count_.has_value()); |
| CHECK_EQ(static_cast<size_t>(*dev_conf->count_), |
| dev_conf->cros_config_cameras_.size()); |
| } |
| |
| return true; |
| } |
| |
| } // namespace cros |