blob: 8d5595e3b6a9334e8da2e4fbaf24aff66fde803d [file] [log] [blame] [edit]
/*
* Copyright 2020 The ChromiumOS Authors
* 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/contains.h>
#include <base/containers/span.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/no_destructor.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/system/sys_info.h>
#include <base/types/expected.h>
#include <brillo/key_value_store.h>
#include <chromeos-config/libcros_config/cros_config.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, std::optional<bool> detachable) 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 &&
(!detachable.has_value() || d.detachable == *detachable);
});
}
const CrosConfigCameraInfo* DeviceConfig::GetCrosConfigInfoFromFacing(
LensFacing facing) const {
auto iter = std::find_if(
cros_config_cameras_.begin(), cros_config_cameras_.end(),
[=](const CrosConfigCameraInfo& d) { return d.facing == facing; });
return iter != cros_config_cameras_.end() ? &*iter : nullptr;
}
base::span<const PlatformCameraInfo> DeviceConfig::GetPlatformCameraInfo() {
static const base::NoDestructor<std::vector<PlatformCameraInfo>>
platform_cameras(ReadPlatformCameraInfo());
return *platform_cameras;
}
// static
int DeviceConfig::GetArcApiLevel() {
static int value = []() {
brillo::KeyValueStore store;
if (!store.Load(base::FilePath("/etc/lsb-release"))) {
LOGF(ERROR) << "Failed to read lsb-release";
return 0;
}
std::string str;
if (!store.GetString("CHROMEOS_ARC_ANDROID_SDK_VERSION", &str)) {
LOGF(ERROR) << "Failed to read CHROMEOS_ARC_ANDROID_SDK_VERSION";
return 0;
}
int version;
if (!base::StringToInt(str, &version)) {
LOGF(ERROR) << "Invalid CHROMEOS_ARC_ANDROID_SDK_VERSION: " << str
<< " in /etc/lsb-release";
return 0;
}
return version;
}();
return value;
}
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);
LOGF(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())) {
LOGF(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()) {
LOGF(ERROR) << "v4l-subdev node for sensor '" << desc.name
<< "' not found";
continue;
}
LOGF(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()) {
LOGF(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;
}
LOGF(INFO) << "Probing media device '" << name.value() << "'";
ProbeMediaController(fd.get());
}
}
enum class EepromReadError {
kReadFailure,
kNotCamera,
};
std::map<base::FilePath, base::FilePath> GetNvmemToEepromPathMap() {
auto matching_length = [](const base::FilePath& a, const base::FilePath& b) {
return std::distance(
a.value().begin(),
std::mismatch(a.value().begin(), a.value().end(), b.value().begin())
.first);
};
std::map<base::FilePath, base::FilePath> result;
base::FileEnumerator nvmem_enum(base::FilePath(kSysfsNvmemDevicesRoot), false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = nvmem_enum.Next(); !path.empty();
path = nvmem_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 (path.BaseName().value().find("nvm_active") == 0) {
LOGF(INFO) << "Ignoring nvmem at " << path;
continue;
}
base::FilePath nvmem_path =
base::MakeAbsoluteFilePath(path.Append("nvmem"));
if (!nvmem_path.empty()) {
result[std::move(nvmem_path)] = base::FilePath();
}
}
if (result.empty()) {
return {};
}
// Find the corresponding nvmem file for each eeprom file on the I2C bus by
// matching the sysfs path.
base::FileEnumerator i2c_enum(base::FilePath(kSysfsI2cDevicesRoot), false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = i2c_enum.Next(); !path.empty();
path = i2c_enum.Next()) {
base::FilePath eeprom_path =
base::MakeAbsoluteFilePath(path.Append("eeprom"));
if (eeprom_path.empty()) {
continue;
}
auto it =
std::max_element(result.begin(), result.end(), [&](auto& a, auto& b) {
return matching_length(eeprom_path, a.first) <
matching_length(eeprom_path, b.first);
});
if (!it->second.empty()) {
LOGF(WARNING) << it->first << " matched to both " << it->second << " and "
<< eeprom_path;
}
it->second = std::move(eeprom_path);
}
return result;
}
void DeviceConfig::AddCameraEeproms() {
auto read_eeprom = [&](base::FilePath from_path)
-> base::expected<EepromIdBlock, EepromReadError> {
std::string content;
if (!base::ReadFileToString(from_path, &content)) {
return base::unexpected(EepromReadError::kReadFailure);
}
std::optional<EepromIdBlock> id_block = FindCameraEepromIdBlock(content);
if (!id_block.has_value()) {
// Not a camera EEPROM. Ignore the device.
return base::unexpected(EepromReadError::kNotCamera);
}
LOGF(INFO) << "Read camera eeprom from " << from_path;
return id_block.value();
};
for (auto& [nvmem_path, eeprom_path] : GetNvmemToEepromPathMap()) {
base::expected<EepromIdBlock, EepromReadError> id_block =
read_eeprom(nvmem_path);
if (!id_block.has_value() &&
id_block.error() == EepromReadError::kReadFailure) {
// 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).
if (!eeprom_path.empty()) {
id_block = read_eeprom(eeprom_path);
}
}
if (!id_block.has_value()) {
continue;
}
eeproms_.push_back(EepromInfo{
.id_block = *id_block,
.nvmem_path = std::move(nvmem_path),
});
}
}
std::vector<PlatformCameraInfo> DeviceConfig::ReadPlatformCameraInfo() {
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;
std::vector<PlatformCameraInfo> platform_cameras;
for (const EepromInfo& eeprom : eeproms_) {
std::vector<std::string> path = eeprom.nvmem_path.GetComponents();
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();
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,
});
}
}
return platform_cameras;
}
// static
bool DeviceConfig::PopulateCrosConfigCameraInfo(DeviceConfig* dev_conf) {
CHECK(dev_conf);
brillo::CrosConfig cros_config;
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) {
const std::string device_path = base::StringPrintf("/camera/devices/%i", i);
std::string interface;
if (!cros_config.GetString(device_path, "interface", &interface)) {
break;
}
std::string facing, orientation, detachable, has_privacy_switch;
CHECK(cros_config.GetString(device_path, "facing", &facing));
CHECK(cros_config.GetString(device_path, "orientation", &orientation));
// Assume non-detachable if the key doesn't exist.
cros_config.GetString(device_path, "detachable", &detachable);
// Assume no privacy switch if the key doesn't exist.
cros_config.GetString(device_path, "has-privacy-switch",
&has_privacy_switch);
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),
.detachable = detachable == "true",
.has_privacy_switch = has_privacy_switch == "true",
});
}
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