blob: 1a5759cc74099dc4527ada759ace9c48d1732da8 [file] [log] [blame]
/* Copyright 2018 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 <fcntl.h>
#include <linux/media.h>
#include <sys/ioctl.h>
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <base/at_exit.h>
#include <base/command_line.h>
#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/json/json_writer.h>
#include <base/logging.h>
#include <base/optional.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/values.h>
#include <brillo/syslog_logging.h>
#include "tools/crc_ccitt.h"
namespace {
const char kSysfsV4lClassRoot[] = "/sys/class/video4linux";
const char kSysfsNvmemDevicesRoot[] = "/sys/bus/nvmem/devices";
const char kVendorIdPath[] = "device/vendor_id";
const std::vector<std::string> kArgsPattern = {"modules", "list"};
const size_t kEepromIdBlockAlignment = 32u;
struct EepromIdBlock {
char os[4];
uint16_t crc;
uint8_t version;
uint8_t length;
uint16_t data_format;
uint16_t module_pid;
char module_vid[2];
char sensor_vid[2];
uint16_t sensor_pid;
};
struct EepromInfo {
EepromIdBlock id_block;
base::FilePath nvmem_path;
};
struct V4L2SensorInfo {
std::string name;
std::string vendor_id;
base::FilePath subdev_path;
};
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;
}
base::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 base::nullopt;
}
class CameraTool {
private:
struct Camera {
const EepromInfo* eeprom;
const V4L2SensorInfo* v4l2_sensor;
std::string sysfs_name;
};
public:
void PrintCameras() {
const std::vector<Camera> cameras = GetPlatformCameras();
base::Value root(base::Value::Type::LIST);
for (const auto& camera : cameras) {
base::Value node(base::Value::Type::DICTIONARY);
if (camera.eeprom != nullptr) {
const EepromIdBlock& b = camera.eeprom->id_block;
node.SetStringKey("name", camera.sysfs_name);
node.SetStringKey("module_id",
base::StringPrintf("%c%c%04x", b.module_vid[0],
b.module_vid[1], b.module_pid));
node.SetStringKey("sensor_id",
base::StringPrintf("%c%c%04x", b.sensor_vid[0],
b.sensor_vid[1], b.sensor_pid));
} else {
CHECK_NE(camera.v4l2_sensor, nullptr);
node.SetStringKey("name", camera.v4l2_sensor->name);
node.SetStringKey("vendor", camera.v4l2_sensor->vendor_id);
}
root.Append(std::move(node));
}
std::string json;
if (!base::JSONWriter::WriteWithOptions(
root, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json)) {
LOG(ERROR) << "Failed to print camera infos";
}
std::cout << json << std::endl;
}
private:
void 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 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 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 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()) {
int fd = open(name.value().c_str(), O_RDWR);
if (fd < 0) {
LOG(ERROR) << "Failed to open '" << name.value() << "'";
continue;
}
LOG(INFO) << "Probing media device '" << name.value() << "'";
ProbeMediaController(fd);
close(fd);
}
}
void AddCameraEeproms() {
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()) {
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::string nvmem;
if (!base::ReadFileToString(nvmem_path, &nvmem)) {
LOG(ERROR) << "Failed to read nvmem from " << nvmem_path;
continue;
}
base::Optional<EepromIdBlock> id_block = FindCameraEepromIdBlock(nvmem);
if (!id_block.has_value())
continue;
LOG(INFO) << "Found camera eeprom on " << nvmem_path;
eeproms_.push_back(EepromInfo{
.id_block = *id_block,
.nvmem_path = std::move(nvmem_path),
});
}
}
std::vector<Camera> GetPlatformCameras() {
if (eeproms_.empty())
AddCameraEeproms();
if (v4l2_sensors_.empty())
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::vector<Camera> cameras;
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());
});
const V4L2SensorInfo* sensor =
iter != v4l2_sensors_.end() ? &*iter : nullptr;
cameras.push_back(Camera{
.eeprom = &eeprom,
.v4l2_sensor = sensor,
.sysfs_name = path[path.size() - 4] + '/' + path[path.size() - 3],
});
if (sensor != nullptr)
associated_sensors.insert(sensor);
}
for (const V4L2SensorInfo& sensor : v4l2_sensors_) {
if (!base::Contains(associated_sensors, &sensor)) {
cameras.push_back(Camera{
.v4l2_sensor = &sensor,
});
}
}
return cameras;
}
private:
std::vector<EepromInfo> eeproms_;
std::vector<V4L2SensorInfo> v4l2_sensors_;
};
bool StringEqualsCaseInsensitiveASCII(const std::string& a,
const std::string& b) {
return base::EqualsCaseInsensitiveASCII(a, b);
}
} // namespace
int main(int argc, char* argv[]) {
// Init CommandLine for InitLogging.
base::CommandLine::Init(argc, argv);
base::AtExitManager at_exit_manager;
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty);
// FIXME: Currently only "modules list" command is supported
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
const std::vector<std::string>& args = cl->GetArgs();
if (cl->GetArgs().empty() ||
!std::equal(kArgsPattern.begin(), kArgsPattern.end(), args.begin(),
StringEqualsCaseInsensitiveASCII)) {
LOG(ERROR) << "Invalid command.";
LOG(ERROR) << "Try following supported commands:";
LOG(ERROR) << " modules - operations on camera modules";
LOG(ERROR) << " list - print available modules";
return 1;
}
CameraTool tool;
tool.PrintCameras();
return 0;
}