[libcamera-devel,4/7] android: Add CameraHalConfig class
diff mbox series

Message ID 20210324112527.63701-5-jacopo@jmondi.org
State Superseded
Delegated to: Jacopo Mondi
Headers show
Series
  • android: Add support for HAL configuration file
Related show

Commit Message

Jacopo Mondi March 24, 2021, 11:25 a.m. UTC
Add a CameraHalConfig class to the Android Camera3 HAL layer.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
 src/android/camera_hal_config.h   |  54 +++++
 src/android/meson.build           |   1 +
 3 files changed, 440 insertions(+)
 create mode 100644 src/android/camera_hal_config.cpp
 create mode 100644 src/android/camera_hal_config.h

Comments

Niklas Söderlund March 25, 2021, 10:33 p.m. UTC | #1
Hi Jacopo,

Thanks for your work.

On 2021-03-24 12:25:24 +0100, Jacopo Mondi wrote:
> Add a CameraHalConfig class to the Android Camera3 HAL layer.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>

There are a few small nits below but I think this is a great step in the 
right direction.

Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>

> ---
>  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
>  src/android/camera_hal_config.h   |  54 +++++
>  src/android/meson.build           |   1 +
>  3 files changed, 440 insertions(+)
>  create mode 100644 src/android/camera_hal_config.cpp
>  create mode 100644 src/android/camera_hal_config.h
> 
> diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> new file mode 100644
> index 000000000000..7e33dfb750ff
> --- /dev/null
> +++ b/src/android/camera_hal_config.cpp
> @@ -0,0 +1,385 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2021, Google Inc.
> + *
> + * camera_hal_config.cpp - Camera HAL configuration file manager
> + */
> +#include "camera_hal_config.h"
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "libcamera/internal/file.h"
> +#include "libcamera/internal/log.h"
> +
> +using namespace libcamera;
> +
> +LOG_DEFINE_CATEGORY(HALConfig)
> +
> +const std::string CameraHalConfig::CameraBlock::toString() const
> +{
> +	std::stringstream ss;
> +
> +	ss << "\'" << name << "\'"
> +	   << " model: " << model
> +	   << "(" << location << ")[" << rotation << "]";

nit: The usage of the pattern "key: <value>" have been discouraged in 
the core, do we extend the same to the HAL?

> +
> +	return ss.str();
> +}
> +
> +CameraHalConfig::CameraHalConfig()
> +{
> +	if (!yaml_parser_initialize(&parser_))
> +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";

Fatal?

> +}
> +
> +CameraHalConfig::~CameraHalConfig()
> +{
> +	yaml_parser_delete(&parser_);
> +}
> +
> +/*
> + * Configuration files can be stored in system paths, which are identified
> + * through the build configuration.
> + *
> + * However, when running uninstalled - the source location takes precedence.
> + */
> +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> +{
> +	static std::array<std::string, 2> searchPaths = {
> +		LIBCAMERA_SYSCONF_DIR,
> +		LIBCAMERA_DATA_DIR,
> +	};
> +
> +	if (File::exists(filename))
> +		return filename;
> +
> +	std::string root = utils::libcameraSourcePath();
> +	if (!root.empty()) {
> +		std::string configurationPath = root + "data/" + filename;
> +
> +		if (File::exists(configurationPath))
> +			return configurationPath;
> +	}
> +
> +	for (std::string &path : searchPaths) {
> +		std::string configurationPath = path + "/" + filename;
> +		if (File::exists(configurationPath))
> +			return configurationPath;
> +	}
> +
> +	return "";
> +}
> +
> +/*
> + * \brief Open the HAL configuration file and validate its content
> + * \return 0 on success, a negative error code otherwise

nit: We don't Doxygen the HAL is it not confusing to use it mark up here 
then?

> + */
> +int CameraHalConfig::open()
> +{
> +	int ret;
> +
> +	const std::string configPath = "camera_hal.yaml";

s/configPath/configFile/ ?

> +	const std::string filePath = findFilePath(configPath);

As this is the only call site would it make sens to have findFilePath() 
return a FILE * to get compartmentalizing of the error paths?

> +	if (filePath.empty()) {
> +		LOG(HALConfig, Warning)
> +			<< "File: \"" << configPath << "\" not found";
> +		return -ENOENT;
> +	}
> +
> +	FILE *fh = fopen(filePath.c_str(), "r");
> +	if (!fh) {
> +		ret = -errno;
> +		LOG(HALConfig, Error) << "Failed to open file " << filePath
> +				      << ": " << strerror(-ret);
> +		return ret;
> +	}
> +
> +	yaml_parser_set_input_file(&parser_, fh);
> +
> +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> +
> +	ret = parseConfigFile();
> +	fclose(fh);
> +	if (ret)
> +		return ret;
> +
> +	LOG(HALConfig, Info) << "Device model: " << model_;
> +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> +	for (const auto &c : cameras_) {
> +		const CameraBlock camera = c.second;
> +		LOG(HALConfig, Info) << camera.toString();
> +	}
> +
> +	return 0;
> +}
> +
> +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> +{
> +	static const std::string empty("");
> +	const auto &it = cameras_.find(camera);
> +	if (it == cameras_.end()) {
> +		LOG(HALConfig, Error)
> +			<< "Camera '" << camera
> +			<< "' not described in the HAL configuration file";
> +		return empty;
> +	}
> +
> +	const CameraBlock &cam = it->second;
> +	return cam.location;
> +}
> +
> +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> +{
> +	static const std::string empty("");

empty not used.

> +	const auto &it = cameras_.find(camera);
> +	if (it == cameras_.end()) {
> +		LOG(HALConfig, Error)
> +			<< "Camera '" << camera
> +			<< "' not described in the HAL configuration file";
> +		return 0;
> +	}
> +
> +	const CameraBlock &cam = it->second;
> +	return cam.rotation;
> +}
> +
> +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> +{
> +	static const std::string empty("");
> +	const auto &it = cameras_.find(camera);
> +	if (it == cameras_.end()) {
> +		LOG(HALConfig, Error)
> +			<< "Camera '" << camera
> +			<< "' not described in the HAL configuration file";
> +		return empty;
> +	}
> +
> +	const CameraBlock &cam = it->second;
> +	return cam.model;
> +}
> +
> +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> +{
> +	/* Make sure the token type is a value and get its content. */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_VALUE_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +	yaml_token_delete(&token);
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_SCALAR_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +
> +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> +	std::string value(scalar, token.data.scalar.length);
> +	yaml_token_delete(&token);
> +
> +	return value;
> +}
> +
> +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> +{
> +	/* Make sure the token type is a key and get its value. */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_KEY_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +	yaml_token_delete(&token);
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_SCALAR_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +
> +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> +	std::string key(scalar, token.data.scalar.length);
> +	yaml_token_delete(&token);
> +
> +	return key;
> +}
> +
> +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> +{
> +	/* The 'camera' block is a VALUE token type. */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_VALUE_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	/*
> +	 * Parse the content of the camera block until we have an unbalanced
> +	 * BLOCK_END_TOKEN which closes the camera block.
> +	 *
> +	 * Add a safety counter to make sure we don't loop indefinitely in case
> +	 * the configuration file is malformed.
> +	 */
> +	unsigned int sentinel = 100;
> +	CameraBlock cameraBlock{};
> +	int blockCounter = 0;
> +	do {
> +		yaml_parser_scan(&parser_, &token);
> +		switch (token.type) {
> +		case YAML_BLOCK_ENTRY_TOKEN:
> +			yaml_token_delete(&token);
> +			blockCounter++;
> +			break;
> +		case YAML_BLOCK_END_TOKEN:
> +			yaml_token_delete(&token);
> +			blockCounter--;
> +			break;
> +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> +			yaml_token_delete(&token);
> +
> +			std::string key = parseKey(token);
> +			std::string value = parseValue(token);
> +			if (key.empty() || value.empty()) {
> +				LOG(HALConfig, Error)
> +					<< "Configuration file is not valid";
> +				return -EINVAL;
> +			}
> +
> +			/* Validate that the parsed key is valid. */
> +			if (key == "location") {
> +				if (value != "front" && value != "back" &&
> +				    value != "external") {
> +					LOG(HALConfig, Error)
> +						<< "Unknown location: " << value;
> +					return -EINVAL;
> +				}
> +				cameraBlock.location = value;
> +			} else if (key == "rotation") {
> +				cameraBlock.rotation = strtoul(value.c_str(),
> +							       NULL, 10);
> +				if (cameraBlock.rotation < 0) {
> +					LOG(HALConfig, Error)
> +						<< "Unknown rotation: "
> +						<< cameraBlock.rotation;
> +					return -EINVAL;
> +				}
> +			} else if (key == "name") {
> +				cameraBlock.name = value;
> +			} else if (key == "model") {
> +				cameraBlock.model = value;
> +			} else {
> +				LOG(HALConfig, Error)
> +					<< "Configuration file is not valid "
> +					<< "unknown key: " << key;
> +				return -EINVAL;
> +			}
> +			break;
> +		}
> +		default:
> +			yaml_token_delete(&token);
> +			break;
> +		}
> +		--sentinel;
> +	} while (blockCounter >= 0 && sentinel);
> +	if (!sentinel) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> +		return -EINVAL;
> +	}
> +
> +	cameras_[cameraBlock.name] = cameraBlock;
> +
> +	return 0;
> +}
> +
> +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> +{
> +	int ret;
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	std::string key = parseKey(token);
> +	if (key.empty()) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +
> +	if (key == "camera") {
> +		yaml_token_delete(&token);
> +		ret = parseCameraBlock(token);
> +		if (ret)
> +			return ret;
> +	} else if (key == "manufacturer") {
> +		yaml_token_delete(&token);
> +		maker_ = parseValue(token);
> +		if (maker_.empty()) {
> +			LOG(HALConfig, Error) << "Configuration file is not valid";
> +			return -EINVAL;
> +		}
> +	} else if (key == "model") {
> +		yaml_token_delete(&token);
> +		model_ = parseValue(token);
> +		if (model_.empty()) {
> +			LOG(HALConfig, Error) << "Configuration file is not valid";
> +			return -EINVAL;
> +		}
> +	} else {
> +		yaml_token_delete(&token);
> +		LOG(HALConfig, Error) << "Unknown key " << key;
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +int CameraHalConfig::parseConfigFile()
> +{
> +	yaml_token_t token;
> +	int ret;
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_STREAM_START_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	/*
> +	 * Start parsing the content of the configuration file which
> +	 * starts as with sequence block.
> +	 */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	/* Parse the single entry blocks. */
> +	do {
> +		yaml_parser_scan(&parser_, &token);
> +		switch (token.type) {
> +		case YAML_BLOCK_ENTRY_TOKEN:
> +			yaml_token_delete(&token);
> +			ret = parseEntryBlock(token);
> +			break;
> +		case YAML_STREAM_END_TOKEN:
> +			/* Resources are released after the loop exit. */
> +			break;
> +		default:
> +			/* Release resources before re-parsing. */
> +			yaml_token_delete(&token);
> +			break;
> +		}
> +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> +	yaml_token_delete(&token);
> +
> +	return ret;
> +}
> diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> new file mode 100644
> index 000000000000..69d42658e90a
> --- /dev/null
> +++ b/src/android/camera_hal_config.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2021, Google Inc.
> + *
> + * camera_hal_config.h - Camera HAL configuration file manager
> + */
> +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> +
> +#include <map>
> +#include <string>
> +#include <yaml.h>
> +
> +class CameraHalConfig
> +{
> +public:
> +	CameraHalConfig();
> +	~CameraHalConfig();
> +
> +	int open();
> +
> +	const std::string &deviceModel() const { return model_; }
> +	const std::string &deviceMaker() const { return maker_; }
> +
> +	const std::string &cameraLocation(const std::string &camera) const;
> +	unsigned int cameraRotation(const std::string &camera) const;
> +	const std::string &cameraModel(const std::string &camera) const;
> +
> +private:
> +	yaml_parser_t parser_;
> +
> +	std::string model_;
> +	std::string maker_;
> +	class CameraBlock
> +	{
> +	public:
> +		std::string name;
> +		std::string location;
> +		std::string model;
> +		unsigned int rotation;
> +
> +		const std::string toString() const;
> +	};
> +	std::map<std::string, CameraBlock> cameras_;
> +
> +	const std::string findFilePath(const std::string &filename);
> +	std::string parseValue(yaml_token_t &token);
> +	std::string parseKey(yaml_token_t &token);
> +	int parseCameraBlock(yaml_token_t &token);
> +	int parseEntryBlock(yaml_token_t &token);
> +	int parseConfigFile();
> +};
> +
> +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> diff --git a/src/android/meson.build b/src/android/meson.build
> index 19f94a4073f1..34e5c494a492 100644
> --- a/src/android/meson.build
> +++ b/src/android/meson.build
> @@ -44,6 +44,7 @@ android_hal_sources = files([
>      'camera3_hal.cpp',
>      'camera_hal_manager.cpp',
>      'camera_device.cpp',
> +    'camera_hal_config.cpp',
>      'camera_metadata.cpp',
>      'camera_ops.cpp',
>      'camera_stream.cpp',
> -- 
> 2.30.0
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Laurent Pinchart March 26, 2021, 4:07 a.m. UTC | #2
Hi Jacopo,

Thank you for the patch.

On Wed, Mar 24, 2021 at 12:25:24PM +0100, Jacopo Mondi wrote:
> Add a CameraHalConfig class to the Android Camera3 HAL layer.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
>  src/android/camera_hal_config.h   |  54 +++++
>  src/android/meson.build           |   1 +
>  3 files changed, 440 insertions(+)
>  create mode 100644 src/android/camera_hal_config.cpp
>  create mode 100644 src/android/camera_hal_config.h
> 
> diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> new file mode 100644
> index 000000000000..7e33dfb750ff
> --- /dev/null
> +++ b/src/android/camera_hal_config.cpp
> @@ -0,0 +1,385 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2021, Google Inc.
> + *
> + * camera_hal_config.cpp - Camera HAL configuration file manager
> + */
> +#include "camera_hal_config.h"
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +
> +#include "libcamera/internal/file.h"
> +#include "libcamera/internal/log.h"
> +
> +using namespace libcamera;
> +
> +LOG_DEFINE_CATEGORY(HALConfig)
> +
> +const std::string CameraHalConfig::CameraBlock::toString() const
> +{
> +	std::stringstream ss;
> +
> +	ss << "\'" << name << "\'"
> +	   << " model: " << model
> +	   << "(" << location << ")[" << rotation << "]";
> +
> +	return ss.str();
> +}

As the amount of information will grow, I don't think we'll be able to
print everything. This function is only used in a single location below,
for debugging purpose, I wonder if we should keep it.

> +
> +CameraHalConfig::CameraHalConfig()
> +{
> +	if (!yaml_parser_initialize(&parser_))
> +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
> +}
> +
> +CameraHalConfig::~CameraHalConfig()
> +{
> +	yaml_parser_delete(&parser_);
> +}
> +
> +/*
> + * Configuration files can be stored in system paths, which are identified
> + * through the build configuration.
> + *
> + * However, when running uninstalled - the source location takes precedence.

Can we run the camera HAL uninstalled ? :-)

> + */
> +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> +{
> +	static std::array<std::string, 2> searchPaths = {
> +		LIBCAMERA_SYSCONF_DIR,
> +		LIBCAMERA_DATA_DIR,

The data dir may not be very useful here, the configuration file is
really system-specific, right ?

I expect we may need to get the configuration file from system-specific
locations, depending on whether we run on Chrome OS or Android, but we
can handle that later.

> +	};
> +
> +	if (File::exists(filename))
> +		return filename;
> +
> +	std::string root = utils::libcameraSourcePath();
> +	if (!root.empty()) {
> +		std::string configurationPath = root + "data/" + filename;
> +
> +		if (File::exists(configurationPath))
> +			return configurationPath;
> +	}
> +
> +	for (std::string &path : searchPaths) {
> +		std::string configurationPath = path + "/" + filename;
> +		if (File::exists(configurationPath))
> +			return configurationPath;
> +	}
> +
> +	return "";
> +}
> +
> +/*
> + * \brief Open the HAL configuration file and validate its content
> + * \return 0 on success, a negative error code otherwise
> + */
> +int CameraHalConfig::open()
> +{
> +	int ret;
> +
> +	const std::string configPath = "camera_hal.yaml";
> +	const std::string filePath = findFilePath(configPath);
> +	if (filePath.empty()) {
> +		LOG(HALConfig, Warning)

This can be an Error, as we can't continue.

> +			<< "File: \"" << configPath << "\" not found";

Maybe s/File:/Configuration file/ ?

> +		return -ENOENT;
> +	}
> +
> +	FILE *fh = fopen(filePath.c_str(), "r");
> +	if (!fh) {
> +		ret = -errno;
> +		LOG(HALConfig, Error) << "Failed to open file " << filePath

Same here, "configuration file" ?

> +				      << ": " << strerror(-ret);
> +		return ret;
> +	}
> +
> +	yaml_parser_set_input_file(&parser_, fh);
> +
> +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> +
> +	ret = parseConfigFile();
> +	fclose(fh);
> +	if (ret)
> +		return ret;
> +
> +	LOG(HALConfig, Info) << "Device model: " << model_;
> +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> +	for (const auto &c : cameras_) {
> +		const CameraBlock camera = c.second;
> +		LOG(HALConfig, Info) << camera.toString();
> +	}

I wonder if dumping the parsed file shouldn't be a debugging feature
instead, or even if we need it at all.

> +
> +	return 0;
> +}
> +
> +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> +{
> +	static const std::string empty("");
> +	const auto &it = cameras_.find(camera);
> +	if (it == cameras_.end()) {
> +		LOG(HALConfig, Error)
> +			<< "Camera '" << camera
> +			<< "' not described in the HAL configuration file";
> +		return empty;
> +	}
> +
> +	const CameraBlock &cam = it->second;
> +	return cam.location;
> +}
> +
> +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> +{
> +	static const std::string empty("");
> +	const auto &it = cameras_.find(camera);
> +	if (it == cameras_.end()) {
> +		LOG(HALConfig, Error)
> +			<< "Camera '" << camera
> +			<< "' not described in the HAL configuration file";
> +		return 0;
> +	}
> +
> +	const CameraBlock &cam = it->second;
> +	return cam.rotation;
> +}
> +
> +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> +{
> +	static const std::string empty("");
> +	const auto &it = cameras_.find(camera);
> +	if (it == cameras_.end()) {
> +		LOG(HALConfig, Error)
> +			<< "Camera '" << camera
> +			<< "' not described in the HAL configuration file";
> +		return empty;
> +	}
> +
> +	const CameraBlock &cam = it->second;
> +	return cam.model;
> +}
> +
> +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> +{
> +	/* Make sure the token type is a value and get its content. */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_VALUE_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +	yaml_token_delete(&token);
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_SCALAR_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +
> +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> +	std::string value(scalar, token.data.scalar.length);
> +	yaml_token_delete(&token);
> +
> +	return value;
> +}
> +
> +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> +{
> +	/* Make sure the token type is a key and get its value. */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_KEY_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +	yaml_token_delete(&token);
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_SCALAR_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return "";
> +	}
> +
> +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> +	std::string key(scalar, token.data.scalar.length);
> +	yaml_token_delete(&token);
> +
> +	return key;
> +}
> +
> +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> +{
> +	/* The 'camera' block is a VALUE token type. */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_VALUE_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	/*
> +	 * Parse the content of the camera block until we have an unbalanced
> +	 * BLOCK_END_TOKEN which closes the camera block.
> +	 *
> +	 * Add a safety counter to make sure we don't loop indefinitely in case
> +	 * the configuration file is malformed.
> +	 */
> +	unsigned int sentinel = 100;
> +	CameraBlock cameraBlock{};
> +	int blockCounter = 0;
> +	do {
> +		yaml_parser_scan(&parser_, &token);
> +		switch (token.type) {
> +		case YAML_BLOCK_ENTRY_TOKEN:
> +			yaml_token_delete(&token);
> +			blockCounter++;
> +			break;
> +		case YAML_BLOCK_END_TOKEN:
> +			yaml_token_delete(&token);
> +			blockCounter--;
> +			break;
> +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> +			yaml_token_delete(&token);
> +
> +			std::string key = parseKey(token);
> +			std::string value = parseValue(token);
> +			if (key.empty() || value.empty()) {
> +				LOG(HALConfig, Error)
> +					<< "Configuration file is not valid";
> +				return -EINVAL;
> +			}
> +
> +			/* Validate that the parsed key is valid. */
> +			if (key == "location") {
> +				if (value != "front" && value != "back" &&
> +				    value != "external") {
> +					LOG(HALConfig, Error)
> +						<< "Unknown location: " << value;
> +					return -EINVAL;
> +				}
> +				cameraBlock.location = value;
> +			} else if (key == "rotation") {
> +				cameraBlock.rotation = strtoul(value.c_str(),
> +							       NULL, 10);
> +				if (cameraBlock.rotation < 0) {
> +					LOG(HALConfig, Error)
> +						<< "Unknown rotation: "
> +						<< cameraBlock.rotation;
> +					return -EINVAL;
> +				}
> +			} else if (key == "name") {
> +				cameraBlock.name = value;
> +			} else if (key == "model") {
> +				cameraBlock.model = value;
> +			} else {
> +				LOG(HALConfig, Error)
> +					<< "Configuration file is not valid "
> +					<< "unknown key: " << key;
> +				return -EINVAL;
> +			}
> +			break;
> +		}
> +		default:
> +			yaml_token_delete(&token);
> +			break;
> +		}
> +		--sentinel;
> +	} while (blockCounter >= 0 && sentinel);
> +	if (!sentinel) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> +		return -EINVAL;
> +	}
> +
> +	cameras_[cameraBlock.name] = cameraBlock;
> +
> +	return 0;
> +}
> +
> +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> +{
> +	int ret;
> +
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	std::string key = parseKey(token);
> +	if (key.empty()) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +
> +	if (key == "camera") {
> +		yaml_token_delete(&token);
> +		ret = parseCameraBlock(token);
> +		if (ret)
> +			return ret;
> +	} else if (key == "manufacturer") {
> +		yaml_token_delete(&token);
> +		maker_ = parseValue(token);
> +		if (maker_.empty()) {
> +			LOG(HALConfig, Error) << "Configuration file is not valid";
> +			return -EINVAL;
> +		}
> +	} else if (key == "model") {
> +		yaml_token_delete(&token);
> +		model_ = parseValue(token);
> +		if (model_.empty()) {
> +			LOG(HALConfig, Error) << "Configuration file is not valid";
> +			return -EINVAL;
> +		}
> +	} else {
> +		yaml_token_delete(&token);
> +		LOG(HALConfig, Error) << "Unknown key " << key;
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +int CameraHalConfig::parseConfigFile()
> +{
> +	yaml_token_t token;
> +	int ret;
> +
> +	yaml_parser_scan(&parser_, &token);

Just to make sure you're aware of it, there's yaml_parser_load() that
produces a parsed document that can then be walked. There's nothing
wrong about using event-based parsing as you do though.

I've skipped review of the parser implementation itself, as it may be
changed depending on how patch 7/7 evolves.

> +	if (token.type != YAML_STREAM_START_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return EINVAL;

-EINVAL

> +	}
> +	yaml_token_delete(&token);
> +
> +	/*
> +	 * Start parsing the content of the configuration file which
> +	 * starts as with sequence block.

s/as // ?

> +	 */
> +	yaml_parser_scan(&parser_, &token);
> +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> +		LOG(HALConfig, Error) << "Configuration file is not valid";
> +		return -EINVAL;
> +	}
> +	yaml_token_delete(&token);
> +
> +	/* Parse the single entry blocks. */
> +	do {
> +		yaml_parser_scan(&parser_, &token);
> +		switch (token.type) {
> +		case YAML_BLOCK_ENTRY_TOKEN:
> +			yaml_token_delete(&token);
> +			ret = parseEntryBlock(token);
> +			break;
> +		case YAML_STREAM_END_TOKEN:
> +			/* Resources are released after the loop exit. */
> +			break;
> +		default:
> +			/* Release resources before re-parsing. */
> +			yaml_token_delete(&token);
> +			break;
> +		}
> +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> +	yaml_token_delete(&token);
> +
> +	return ret;
> +}
> diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> new file mode 100644
> index 000000000000..69d42658e90a
> --- /dev/null
> +++ b/src/android/camera_hal_config.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2021, Google Inc.
> + *
> + * camera_hal_config.h - Camera HAL configuration file manager
> + */
> +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> +
> +#include <map>
> +#include <string>
> +#include <yaml.h>

It would be nice to hide the YAML perser from this header. It could be
done by moving the parsing to a CameraHalConfigParser class, private to
the camera_hal_config.cpp file.

> +
> +class CameraHalConfig
> +{
> +public:
> +	CameraHalConfig();
> +	~CameraHalConfig();
> +
> +	int open();
> +
> +	const std::string &deviceModel() const { return model_; }
> +	const std::string &deviceMaker() const { return maker_; }
> +
> +	const std::string &cameraLocation(const std::string &camera) const;
> +	unsigned int cameraRotation(const std::string &camera) const;
> +	const std::string &cameraModel(const std::string &camera) const;
> +
> +private:
> +	yaml_parser_t parser_;
> +
> +	std::string model_;
> +	std::string maker_;
> +	class CameraBlock
> +	{
> +	public:
> +		std::string name;
> +		std::string location;
> +		std::string model;
> +		unsigned int rotation;
> +
> +		const std::string toString() const;
> +	};

How about making this class public (and probably renaming it), and replacing the last three public
functions with

	const CameraBlock *cameraConfig(const std::string &name) const;

? That way each CameraDevice could store a reference to the
corresponding camera configuration only.

> +	std::map<std::string, CameraBlock> cameras_;
> +
> +	const std::string findFilePath(const std::string &filename);

No need for const at the beginning as you return by value, but you can
add a const at the end.

> +	std::string parseValue(yaml_token_t &token);
> +	std::string parseKey(yaml_token_t &token);
> +	int parseCameraBlock(yaml_token_t &token);
> +	int parseEntryBlock(yaml_token_t &token);
> +	int parseConfigFile();
> +};
> +
> +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> diff --git a/src/android/meson.build b/src/android/meson.build
> index 19f94a4073f1..34e5c494a492 100644
> --- a/src/android/meson.build
> +++ b/src/android/meson.build
> @@ -44,6 +44,7 @@ android_hal_sources = files([
>      'camera3_hal.cpp',
>      'camera_hal_manager.cpp',
>      'camera_device.cpp',
> +    'camera_hal_config.cpp',
>      'camera_metadata.cpp',
>      'camera_ops.cpp',
>      'camera_stream.cpp',
Jacopo Mondi March 29, 2021, 2:23 p.m. UTC | #3
Hi Niklas,

On Thu, Mar 25, 2021 at 11:33:31PM +0100, Niklas Söderlund wrote:
> Hi Jacopo,
>
> Thanks for your work.
>
> On 2021-03-24 12:25:24 +0100, Jacopo Mondi wrote:
> > Add a CameraHalConfig class to the Android Camera3 HAL layer.
> >
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
>
> There are a few small nits below but I think this is a great step in the
> right direction.
>
> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
>
> > ---
> >  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
> >  src/android/camera_hal_config.h   |  54 +++++
> >  src/android/meson.build           |   1 +
> >  3 files changed, 440 insertions(+)
> >  create mode 100644 src/android/camera_hal_config.cpp
> >  create mode 100644 src/android/camera_hal_config.h
> >
> > diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> > new file mode 100644
> > index 000000000000..7e33dfb750ff
> > --- /dev/null
> > +++ b/src/android/camera_hal_config.cpp
> > @@ -0,0 +1,385 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Google Inc.
> > + *
> > + * camera_hal_config.cpp - Camera HAL configuration file manager
> > + */
> > +#include "camera_hal_config.h"
> > +
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +
> > +#include "libcamera/internal/file.h"
> > +#include "libcamera/internal/log.h"
> > +
> > +using namespace libcamera;
> > +
> > +LOG_DEFINE_CATEGORY(HALConfig)
> > +
> > +const std::string CameraHalConfig::CameraBlock::toString() const
> > +{
> > +	std::stringstream ss;
> > +
> > +	ss << "\'" << name << "\'"
> > +	   << " model: " << model
> > +	   << "(" << location << ")[" << rotation << "]";
>
> nit: The usage of the pattern "key: <value>" have been discouraged in
> the core, do we extend the same to the HAL?
>

Not sure what you mean here -.-

> > +
> > +	return ss.str();
> > +}
> > +
> > +CameraHalConfig::CameraHalConfig()
> > +{
> > +	if (!yaml_parser_initialize(&parser_))
> > +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
>
> Fatal?
>
> > +}
> > +
> > +CameraHalConfig::~CameraHalConfig()
> > +{
> > +	yaml_parser_delete(&parser_);
> > +}
> > +
> > +/*
> > + * Configuration files can be stored in system paths, which are identified
> > + * through the build configuration.
> > + *
> > + * However, when running uninstalled - the source location takes precedence.
> > + */
> > +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> > +{
> > +	static std::array<std::string, 2> searchPaths = {
> > +		LIBCAMERA_SYSCONF_DIR,
> > +		LIBCAMERA_DATA_DIR,
> > +	};
> > +
> > +	if (File::exists(filename))
> > +		return filename;
> > +
> > +	std::string root = utils::libcameraSourcePath();
> > +	if (!root.empty()) {
> > +		std::string configurationPath = root + "data/" + filename;
> > +
> > +		if (File::exists(configurationPath))
> > +			return configurationPath;
> > +	}
> > +
> > +	for (std::string &path : searchPaths) {
> > +		std::string configurationPath = path + "/" + filename;
> > +		if (File::exists(configurationPath))
> > +			return configurationPath;
> > +	}
> > +
> > +	return "";
> > +}
> > +
> > +/*
> > + * \brief Open the HAL configuration file and validate its content
> > + * \return 0 on success, a negative error code otherwise
>
> nit: We don't Doxygen the HAL is it not confusing to use it mark up here
> then?
>
> > + */
> > +int CameraHalConfig::open()
> > +{
> > +	int ret;
> > +
> > +	const std::string configPath = "camera_hal.yaml";
>
> s/configPath/configFile/ ?
>
> > +	const std::string filePath = findFilePath(configPath);
>
> As this is the only call site would it make sens to have findFilePath()
> return a FILE * to get compartmentalizing of the error paths?
>
> > +	if (filePath.empty()) {
> > +		LOG(HALConfig, Warning)
> > +			<< "File: \"" << configPath << "\" not found";
> > +		return -ENOENT;
> > +	}
> > +
> > +	FILE *fh = fopen(filePath.c_str(), "r");
> > +	if (!fh) {
> > +		ret = -errno;
> > +		LOG(HALConfig, Error) << "Failed to open file " << filePath
> > +				      << ": " << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	yaml_parser_set_input_file(&parser_, fh);
> > +
> > +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> > +
> > +	ret = parseConfigFile();
> > +	fclose(fh);
> > +	if (ret)
> > +		return ret;
> > +
> > +	LOG(HALConfig, Info) << "Device model: " << model_;
> > +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> > +	for (const auto &c : cameras_) {
> > +		const CameraBlock camera = c.second;
> > +		LOG(HALConfig, Info) << camera.toString();
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> > +{
> > +	static const std::string empty("");
> > +	const auto &it = cameras_.find(camera);
> > +	if (it == cameras_.end()) {
> > +		LOG(HALConfig, Error)
> > +			<< "Camera '" << camera
> > +			<< "' not described in the HAL configuration file";
> > +		return empty;
> > +	}
> > +
> > +	const CameraBlock &cam = it->second;
> > +	return cam.location;
> > +}
> > +
> > +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> > +{
> > +	static const std::string empty("");
>
> empty not used.
>

Ouch, leftover

> > +	const auto &it = cameras_.find(camera);
> > +	if (it == cameras_.end()) {
> > +		LOG(HALConfig, Error)
> > +			<< "Camera '" << camera
> > +			<< "' not described in the HAL configuration file";
> > +		return 0;
> > +	}
> > +
> > +	const CameraBlock &cam = it->second;
> > +	return cam.rotation;
> > +}
> > +
> > +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> > +{
> > +	static const std::string empty("");
> > +	const auto &it = cameras_.find(camera);
> > +	if (it == cameras_.end()) {
> > +		LOG(HALConfig, Error)
> > +			<< "Camera '" << camera
> > +			<< "' not described in the HAL configuration file";
> > +		return empty;
> > +	}
> > +
> > +	const CameraBlock &cam = it->second;
> > +	return cam.model;
> > +}
> > +
> > +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> > +{
> > +	/* Make sure the token type is a value and get its content. */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_VALUE_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_SCALAR_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +
> > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > +	std::string value(scalar, token.data.scalar.length);
> > +	yaml_token_delete(&token);
> > +
> > +	return value;
> > +}
> > +
> > +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> > +{
> > +	/* Make sure the token type is a key and get its value. */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_KEY_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_SCALAR_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +
> > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > +	std::string key(scalar, token.data.scalar.length);
> > +	yaml_token_delete(&token);
> > +
> > +	return key;
> > +}
> > +
> > +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> > +{
> > +	/* The 'camera' block is a VALUE token type. */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_VALUE_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	/*
> > +	 * Parse the content of the camera block until we have an unbalanced
> > +	 * BLOCK_END_TOKEN which closes the camera block.
> > +	 *
> > +	 * Add a safety counter to make sure we don't loop indefinitely in case
> > +	 * the configuration file is malformed.
> > +	 */
> > +	unsigned int sentinel = 100;
> > +	CameraBlock cameraBlock{};
> > +	int blockCounter = 0;
> > +	do {
> > +		yaml_parser_scan(&parser_, &token);
> > +		switch (token.type) {
> > +		case YAML_BLOCK_ENTRY_TOKEN:
> > +			yaml_token_delete(&token);
> > +			blockCounter++;
> > +			break;
> > +		case YAML_BLOCK_END_TOKEN:
> > +			yaml_token_delete(&token);
> > +			blockCounter--;
> > +			break;
> > +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> > +			yaml_token_delete(&token);
> > +
> > +			std::string key = parseKey(token);
> > +			std::string value = parseValue(token);
> > +			if (key.empty() || value.empty()) {
> > +				LOG(HALConfig, Error)
> > +					<< "Configuration file is not valid";
> > +				return -EINVAL;
> > +			}
> > +
> > +			/* Validate that the parsed key is valid. */
> > +			if (key == "location") {
> > +				if (value != "front" && value != "back" &&
> > +				    value != "external") {
> > +					LOG(HALConfig, Error)
> > +						<< "Unknown location: " << value;
> > +					return -EINVAL;
> > +				}
> > +				cameraBlock.location = value;
> > +			} else if (key == "rotation") {
> > +				cameraBlock.rotation = strtoul(value.c_str(),
> > +							       NULL, 10);
> > +				if (cameraBlock.rotation < 0) {
> > +					LOG(HALConfig, Error)
> > +						<< "Unknown rotation: "
> > +						<< cameraBlock.rotation;
> > +					return -EINVAL;
> > +				}
> > +			} else if (key == "name") {
> > +				cameraBlock.name = value;
> > +			} else if (key == "model") {
> > +				cameraBlock.model = value;
> > +			} else {
> > +				LOG(HALConfig, Error)
> > +					<< "Configuration file is not valid "
> > +					<< "unknown key: " << key;
> > +				return -EINVAL;
> > +			}
> > +			break;
> > +		}
> > +		default:
> > +			yaml_token_delete(&token);
> > +			break;
> > +		}
> > +		--sentinel;
> > +	} while (blockCounter >= 0 && sentinel);
> > +	if (!sentinel) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> > +		return -EINVAL;
> > +	}
> > +
> > +	cameras_[cameraBlock.name] = cameraBlock;
> > +
> > +	return 0;
> > +}
> > +
> > +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> > +{
> > +	int ret;
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	std::string key = parseKey(token);
> > +	if (key.empty()) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (key == "camera") {
> > +		yaml_token_delete(&token);
> > +		ret = parseCameraBlock(token);
> > +		if (ret)
> > +			return ret;
> > +	} else if (key == "manufacturer") {
> > +		yaml_token_delete(&token);
> > +		maker_ = parseValue(token);
> > +		if (maker_.empty()) {
> > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > +			return -EINVAL;
> > +		}
> > +	} else if (key == "model") {
> > +		yaml_token_delete(&token);
> > +		model_ = parseValue(token);
> > +		if (model_.empty()) {
> > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > +			return -EINVAL;
> > +		}
> > +	} else {
> > +		yaml_token_delete(&token);
> > +		LOG(HALConfig, Error) << "Unknown key " << key;
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int CameraHalConfig::parseConfigFile()
> > +{
> > +	yaml_token_t token;
> > +	int ret;
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_STREAM_START_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	/*
> > +	 * Start parsing the content of the configuration file which
> > +	 * starts as with sequence block.
> > +	 */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	/* Parse the single entry blocks. */
> > +	do {
> > +		yaml_parser_scan(&parser_, &token);
> > +		switch (token.type) {
> > +		case YAML_BLOCK_ENTRY_TOKEN:
> > +			yaml_token_delete(&token);
> > +			ret = parseEntryBlock(token);
> > +			break;
> > +		case YAML_STREAM_END_TOKEN:
> > +			/* Resources are released after the loop exit. */
> > +			break;
> > +		default:
> > +			/* Release resources before re-parsing. */
> > +			yaml_token_delete(&token);
> > +			break;
> > +		}
> > +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> > +	yaml_token_delete(&token);
> > +
> > +	return ret;
> > +}
> > diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> > new file mode 100644
> > index 000000000000..69d42658e90a
> > --- /dev/null
> > +++ b/src/android/camera_hal_config.h
> > @@ -0,0 +1,54 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Google Inc.
> > + *
> > + * camera_hal_config.h - Camera HAL configuration file manager
> > + */
> > +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> > +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> > +
> > +#include <map>
> > +#include <string>
> > +#include <yaml.h>
> > +
> > +class CameraHalConfig
> > +{
> > +public:
> > +	CameraHalConfig();
> > +	~CameraHalConfig();
> > +
> > +	int open();
> > +
> > +	const std::string &deviceModel() const { return model_; }
> > +	const std::string &deviceMaker() const { return maker_; }
> > +
> > +	const std::string &cameraLocation(const std::string &camera) const;
> > +	unsigned int cameraRotation(const std::string &camera) const;
> > +	const std::string &cameraModel(const std::string &camera) const;
> > +
> > +private:
> > +	yaml_parser_t parser_;
> > +
> > +	std::string model_;
> > +	std::string maker_;
> > +	class CameraBlock
> > +	{
> > +	public:
> > +		std::string name;
> > +		std::string location;
> > +		std::string model;
> > +		unsigned int rotation;
> > +
> > +		const std::string toString() const;
> > +	};
> > +	std::map<std::string, CameraBlock> cameras_;
> > +
> > +	const std::string findFilePath(const std::string &filename);
> > +	std::string parseValue(yaml_token_t &token);
> > +	std::string parseKey(yaml_token_t &token);
> > +	int parseCameraBlock(yaml_token_t &token);
> > +	int parseEntryBlock(yaml_token_t &token);
> > +	int parseConfigFile();
> > +};
> > +
> > +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> > diff --git a/src/android/meson.build b/src/android/meson.build
> > index 19f94a4073f1..34e5c494a492 100644
> > --- a/src/android/meson.build
> > +++ b/src/android/meson.build
> > @@ -44,6 +44,7 @@ android_hal_sources = files([
> >      'camera3_hal.cpp',
> >      'camera_hal_manager.cpp',
> >      'camera_device.cpp',
> > +    'camera_hal_config.cpp',
> >      'camera_metadata.cpp',
> >      'camera_ops.cpp',
> >      'camera_stream.cpp',
> > --
> > 2.30.0
> >
> > _______________________________________________
> > libcamera-devel mailing list
> > libcamera-devel@lists.libcamera.org
> > https://lists.libcamera.org/listinfo/libcamera-devel
>
> --
> Regards,
> Niklas Söderlund
Niklas Söderlund March 29, 2021, 2:27 p.m. UTC | #4
Hi Jacopo,

On 2021-03-29 16:23:08 +0200, Jacopo Mondi wrote:
> Hi Niklas,
> 
> On Thu, Mar 25, 2021 at 11:33:31PM +0100, Niklas Söderlund wrote:
> > Hi Jacopo,
> >
> > Thanks for your work.
> >
> > On 2021-03-24 12:25:24 +0100, Jacopo Mondi wrote:
> > > Add a CameraHalConfig class to the Android Camera3 HAL layer.
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> >
> > There are a few small nits below but I think this is a great step in the
> > right direction.
> >
> > Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> >
> > > ---
> > >  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
> > >  src/android/camera_hal_config.h   |  54 +++++
> > >  src/android/meson.build           |   1 +
> > >  3 files changed, 440 insertions(+)
> > >  create mode 100644 src/android/camera_hal_config.cpp
> > >  create mode 100644 src/android/camera_hal_config.h
> > >
> > > diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> > > new file mode 100644
> > > index 000000000000..7e33dfb750ff
> > > --- /dev/null
> > > +++ b/src/android/camera_hal_config.cpp
> > > @@ -0,0 +1,385 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Google Inc.
> > > + *
> > > + * camera_hal_config.cpp - Camera HAL configuration file manager
> > > + */
> > > +#include "camera_hal_config.h"
> > > +
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +
> > > +#include "libcamera/internal/file.h"
> > > +#include "libcamera/internal/log.h"
> > > +
> > > +using namespace libcamera;
> > > +
> > > +LOG_DEFINE_CATEGORY(HALConfig)
> > > +
> > > +const std::string CameraHalConfig::CameraBlock::toString() const
> > > +{
> > > +	std::stringstream ss;
> > > +
> > > +	ss << "\'" << name << "\'"
> > > +	   << " model: " << model
> > > +	   << "(" << location << ")[" << rotation << "]";
> >
> > nit: The usage of the pattern "key: <value>" have been discouraged in
> > the core, do we extend the same to the HAL?
> >
> 
> Not sure what you mean here -.-

Sorry, I just noticed that the pattern of

    << " model: " << model 

Have been discouraged in the core in favor of

    << " model " << model 

> 
> > > +
> > > +	return ss.str();
> > > +}
> > > +
> > > +CameraHalConfig::CameraHalConfig()
> > > +{
> > > +	if (!yaml_parser_initialize(&parser_))
> > > +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
> >
> > Fatal?
> >
> > > +}
> > > +
> > > +CameraHalConfig::~CameraHalConfig()
> > > +{
> > > +	yaml_parser_delete(&parser_);
> > > +}
> > > +
> > > +/*
> > > + * Configuration files can be stored in system paths, which are identified
> > > + * through the build configuration.
> > > + *
> > > + * However, when running uninstalled - the source location takes precedence.
> > > + */
> > > +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> > > +{
> > > +	static std::array<std::string, 2> searchPaths = {
> > > +		LIBCAMERA_SYSCONF_DIR,
> > > +		LIBCAMERA_DATA_DIR,
> > > +	};
> > > +
> > > +	if (File::exists(filename))
> > > +		return filename;
> > > +
> > > +	std::string root = utils::libcameraSourcePath();
> > > +	if (!root.empty()) {
> > > +		std::string configurationPath = root + "data/" + filename;
> > > +
> > > +		if (File::exists(configurationPath))
> > > +			return configurationPath;
> > > +	}
> > > +
> > > +	for (std::string &path : searchPaths) {
> > > +		std::string configurationPath = path + "/" + filename;
> > > +		if (File::exists(configurationPath))
> > > +			return configurationPath;
> > > +	}
> > > +
> > > +	return "";
> > > +}
> > > +
> > > +/*
> > > + * \brief Open the HAL configuration file and validate its content
> > > + * \return 0 on success, a negative error code otherwise
> >
> > nit: We don't Doxygen the HAL is it not confusing to use it mark up here
> > then?
> >
> > > + */
> > > +int CameraHalConfig::open()
> > > +{
> > > +	int ret;
> > > +
> > > +	const std::string configPath = "camera_hal.yaml";
> >
> > s/configPath/configFile/ ?
> >
> > > +	const std::string filePath = findFilePath(configPath);
> >
> > As this is the only call site would it make sens to have findFilePath()
> > return a FILE * to get compartmentalizing of the error paths?
> >
> > > +	if (filePath.empty()) {
> > > +		LOG(HALConfig, Warning)
> > > +			<< "File: \"" << configPath << "\" not found";
> > > +		return -ENOENT;
> > > +	}
> > > +
> > > +	FILE *fh = fopen(filePath.c_str(), "r");
> > > +	if (!fh) {
> > > +		ret = -errno;
> > > +		LOG(HALConfig, Error) << "Failed to open file " << filePath
> > > +				      << ": " << strerror(-ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	yaml_parser_set_input_file(&parser_, fh);
> > > +
> > > +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> > > +
> > > +	ret = parseConfigFile();
> > > +	fclose(fh);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	LOG(HALConfig, Info) << "Device model: " << model_;
> > > +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> > > +	for (const auto &c : cameras_) {
> > > +		const CameraBlock camera = c.second;
> > > +		LOG(HALConfig, Info) << camera.toString();
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> > > +{
> > > +	static const std::string empty("");
> > > +	const auto &it = cameras_.find(camera);
> > > +	if (it == cameras_.end()) {
> > > +		LOG(HALConfig, Error)
> > > +			<< "Camera '" << camera
> > > +			<< "' not described in the HAL configuration file";
> > > +		return empty;
> > > +	}
> > > +
> > > +	const CameraBlock &cam = it->second;
> > > +	return cam.location;
> > > +}
> > > +
> > > +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> > > +{
> > > +	static const std::string empty("");
> >
> > empty not used.
> >
> 
> Ouch, leftover
> 
> > > +	const auto &it = cameras_.find(camera);
> > > +	if (it == cameras_.end()) {
> > > +		LOG(HALConfig, Error)
> > > +			<< "Camera '" << camera
> > > +			<< "' not described in the HAL configuration file";
> > > +		return 0;
> > > +	}
> > > +
> > > +	const CameraBlock &cam = it->second;
> > > +	return cam.rotation;
> > > +}
> > > +
> > > +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> > > +{
> > > +	static const std::string empty("");
> > > +	const auto &it = cameras_.find(camera);
> > > +	if (it == cameras_.end()) {
> > > +		LOG(HALConfig, Error)
> > > +			<< "Camera '" << camera
> > > +			<< "' not described in the HAL configuration file";
> > > +		return empty;
> > > +	}
> > > +
> > > +	const CameraBlock &cam = it->second;
> > > +	return cam.model;
> > > +}
> > > +
> > > +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> > > +{
> > > +	/* Make sure the token type is a value and get its content. */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +
> > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > +	std::string value(scalar, token.data.scalar.length);
> > > +	yaml_token_delete(&token);
> > > +
> > > +	return value;
> > > +}
> > > +
> > > +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> > > +{
> > > +	/* Make sure the token type is a key and get its value. */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_KEY_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +
> > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > +	std::string key(scalar, token.data.scalar.length);
> > > +	yaml_token_delete(&token);
> > > +
> > > +	return key;
> > > +}
> > > +
> > > +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> > > +{
> > > +	/* The 'camera' block is a VALUE token type. */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	/*
> > > +	 * Parse the content of the camera block until we have an unbalanced
> > > +	 * BLOCK_END_TOKEN which closes the camera block.
> > > +	 *
> > > +	 * Add a safety counter to make sure we don't loop indefinitely in case
> > > +	 * the configuration file is malformed.
> > > +	 */
> > > +	unsigned int sentinel = 100;
> > > +	CameraBlock cameraBlock{};
> > > +	int blockCounter = 0;
> > > +	do {
> > > +		yaml_parser_scan(&parser_, &token);
> > > +		switch (token.type) {
> > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > +			yaml_token_delete(&token);
> > > +			blockCounter++;
> > > +			break;
> > > +		case YAML_BLOCK_END_TOKEN:
> > > +			yaml_token_delete(&token);
> > > +			blockCounter--;
> > > +			break;
> > > +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> > > +			yaml_token_delete(&token);
> > > +
> > > +			std::string key = parseKey(token);
> > > +			std::string value = parseValue(token);
> > > +			if (key.empty() || value.empty()) {
> > > +				LOG(HALConfig, Error)
> > > +					<< "Configuration file is not valid";
> > > +				return -EINVAL;
> > > +			}
> > > +
> > > +			/* Validate that the parsed key is valid. */
> > > +			if (key == "location") {
> > > +				if (value != "front" && value != "back" &&
> > > +				    value != "external") {
> > > +					LOG(HALConfig, Error)
> > > +						<< "Unknown location: " << value;
> > > +					return -EINVAL;
> > > +				}
> > > +				cameraBlock.location = value;
> > > +			} else if (key == "rotation") {
> > > +				cameraBlock.rotation = strtoul(value.c_str(),
> > > +							       NULL, 10);
> > > +				if (cameraBlock.rotation < 0) {
> > > +					LOG(HALConfig, Error)
> > > +						<< "Unknown rotation: "
> > > +						<< cameraBlock.rotation;
> > > +					return -EINVAL;
> > > +				}
> > > +			} else if (key == "name") {
> > > +				cameraBlock.name = value;
> > > +			} else if (key == "model") {
> > > +				cameraBlock.model = value;
> > > +			} else {
> > > +				LOG(HALConfig, Error)
> > > +					<< "Configuration file is not valid "
> > > +					<< "unknown key: " << key;
> > > +				return -EINVAL;
> > > +			}
> > > +			break;
> > > +		}
> > > +		default:
> > > +			yaml_token_delete(&token);
> > > +			break;
> > > +		}
> > > +		--sentinel;
> > > +	} while (blockCounter >= 0 && sentinel);
> > > +	if (!sentinel) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	cameras_[cameraBlock.name] = cameraBlock;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> > > +{
> > > +	int ret;
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	std::string key = parseKey(token);
> > > +	if (key.empty()) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	if (key == "camera") {
> > > +		yaml_token_delete(&token);
> > > +		ret = parseCameraBlock(token);
> > > +		if (ret)
> > > +			return ret;
> > > +	} else if (key == "manufacturer") {
> > > +		yaml_token_delete(&token);
> > > +		maker_ = parseValue(token);
> > > +		if (maker_.empty()) {
> > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +			return -EINVAL;
> > > +		}
> > > +	} else if (key == "model") {
> > > +		yaml_token_delete(&token);
> > > +		model_ = parseValue(token);
> > > +		if (model_.empty()) {
> > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +			return -EINVAL;
> > > +		}
> > > +	} else {
> > > +		yaml_token_delete(&token);
> > > +		LOG(HALConfig, Error) << "Unknown key " << key;
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +int CameraHalConfig::parseConfigFile()
> > > +{
> > > +	yaml_token_t token;
> > > +	int ret;
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_STREAM_START_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	/*
> > > +	 * Start parsing the content of the configuration file which
> > > +	 * starts as with sequence block.
> > > +	 */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	/* Parse the single entry blocks. */
> > > +	do {
> > > +		yaml_parser_scan(&parser_, &token);
> > > +		switch (token.type) {
> > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > +			yaml_token_delete(&token);
> > > +			ret = parseEntryBlock(token);
> > > +			break;
> > > +		case YAML_STREAM_END_TOKEN:
> > > +			/* Resources are released after the loop exit. */
> > > +			break;
> > > +		default:
> > > +			/* Release resources before re-parsing. */
> > > +			yaml_token_delete(&token);
> > > +			break;
> > > +		}
> > > +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> > > +	yaml_token_delete(&token);
> > > +
> > > +	return ret;
> > > +}
> > > diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> > > new file mode 100644
> > > index 000000000000..69d42658e90a
> > > --- /dev/null
> > > +++ b/src/android/camera_hal_config.h
> > > @@ -0,0 +1,54 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Google Inc.
> > > + *
> > > + * camera_hal_config.h - Camera HAL configuration file manager
> > > + */
> > > +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> > > +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> > > +
> > > +#include <map>
> > > +#include <string>
> > > +#include <yaml.h>
> > > +
> > > +class CameraHalConfig
> > > +{
> > > +public:
> > > +	CameraHalConfig();
> > > +	~CameraHalConfig();
> > > +
> > > +	int open();
> > > +
> > > +	const std::string &deviceModel() const { return model_; }
> > > +	const std::string &deviceMaker() const { return maker_; }
> > > +
> > > +	const std::string &cameraLocation(const std::string &camera) const;
> > > +	unsigned int cameraRotation(const std::string &camera) const;
> > > +	const std::string &cameraModel(const std::string &camera) const;
> > > +
> > > +private:
> > > +	yaml_parser_t parser_;
> > > +
> > > +	std::string model_;
> > > +	std::string maker_;
> > > +	class CameraBlock
> > > +	{
> > > +	public:
> > > +		std::string name;
> > > +		std::string location;
> > > +		std::string model;
> > > +		unsigned int rotation;
> > > +
> > > +		const std::string toString() const;
> > > +	};
> > > +	std::map<std::string, CameraBlock> cameras_;
> > > +
> > > +	const std::string findFilePath(const std::string &filename);
> > > +	std::string parseValue(yaml_token_t &token);
> > > +	std::string parseKey(yaml_token_t &token);
> > > +	int parseCameraBlock(yaml_token_t &token);
> > > +	int parseEntryBlock(yaml_token_t &token);
> > > +	int parseConfigFile();
> > > +};
> > > +
> > > +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> > > diff --git a/src/android/meson.build b/src/android/meson.build
> > > index 19f94a4073f1..34e5c494a492 100644
> > > --- a/src/android/meson.build
> > > +++ b/src/android/meson.build
> > > @@ -44,6 +44,7 @@ android_hal_sources = files([
> > >      'camera3_hal.cpp',
> > >      'camera_hal_manager.cpp',
> > >      'camera_device.cpp',
> > > +    'camera_hal_config.cpp',
> > >      'camera_metadata.cpp',
> > >      'camera_ops.cpp',
> > >      'camera_stream.cpp',
> > > --
> > > 2.30.0
> > >
> > > _______________________________________________
> > > libcamera-devel mailing list
> > > libcamera-devel@lists.libcamera.org
> > > https://lists.libcamera.org/listinfo/libcamera-devel
> >
> > --
> > Regards,
> > Niklas Söderlund
Jacopo Mondi March 29, 2021, 2:27 p.m. UTC | #5
Hi Laurent,

On Fri, Mar 26, 2021 at 06:07:07AM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> On Wed, Mar 24, 2021 at 12:25:24PM +0100, Jacopo Mondi wrote:
> > Add a CameraHalConfig class to the Android Camera3 HAL layer.
> >
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > ---
> >  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
> >  src/android/camera_hal_config.h   |  54 +++++
> >  src/android/meson.build           |   1 +
> >  3 files changed, 440 insertions(+)
> >  create mode 100644 src/android/camera_hal_config.cpp
> >  create mode 100644 src/android/camera_hal_config.h
> >
> > diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> > new file mode 100644
> > index 000000000000..7e33dfb750ff
> > --- /dev/null
> > +++ b/src/android/camera_hal_config.cpp
> > @@ -0,0 +1,385 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Google Inc.
> > + *
> > + * camera_hal_config.cpp - Camera HAL configuration file manager
> > + */
> > +#include "camera_hal_config.h"
> > +
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +
> > +#include "libcamera/internal/file.h"
> > +#include "libcamera/internal/log.h"
> > +
> > +using namespace libcamera;
> > +
> > +LOG_DEFINE_CATEGORY(HALConfig)
> > +
> > +const std::string CameraHalConfig::CameraBlock::toString() const
> > +{
> > +	std::stringstream ss;
> > +
> > +	ss << "\'" << name << "\'"
> > +	   << " model: " << model
> > +	   << "(" << location << ")[" << rotation << "]";
> > +
> > +	return ss.str();
> > +}
>
> As the amount of information will grow, I don't think we'll be able to
> print everything. This function is only used in a single location below,
> for debugging purpose, I wonder if we should keep it.
>
> > +
> > +CameraHalConfig::CameraHalConfig()
> > +{
> > +	if (!yaml_parser_initialize(&parser_))
> > +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
> > +}
> > +
> > +CameraHalConfig::~CameraHalConfig()
> > +{
> > +	yaml_parser_delete(&parser_);
> > +}
> > +
> > +/*
> > + * Configuration files can be stored in system paths, which are identified
> > + * through the build configuration.
> > + *
> > + * However, when running uninstalled - the source location takes precedence.
>
> Can we run the camera HAL uninstalled ? :-)
>

This meant "libcamera" uninstalled.

> > + */
> > +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> > +{
> > +	static std::array<std::string, 2> searchPaths = {
> > +		LIBCAMERA_SYSCONF_DIR,
> > +		LIBCAMERA_DATA_DIR,
>
> The data dir may not be very useful here, the configuration file is
> really system-specific, right ?
>
> I expect we may need to get the configuration file from system-specific
> locations, depending on whether we run on Chrome OS or Android, but we
> can handle that later.
>
> > +	};
> > +
> > +	if (File::exists(filename))
> > +		return filename;
> > +
> > +	std::string root = utils::libcameraSourcePath();
> > +	if (!root.empty()) {
> > +		std::string configurationPath = root + "data/" + filename;
> > +
> > +		if (File::exists(configurationPath))
> > +			return configurationPath;
> > +	}
> > +
> > +	for (std::string &path : searchPaths) {
> > +		std::string configurationPath = path + "/" + filename;
> > +		if (File::exists(configurationPath))
> > +			return configurationPath;
> > +	}
> > +
> > +	return "";
> > +}
> > +
> > +/*
> > + * \brief Open the HAL configuration file and validate its content
> > + * \return 0 on success, a negative error code otherwise
> > + */
> > +int CameraHalConfig::open()
> > +{
> > +	int ret;
> > +
> > +	const std::string configPath = "camera_hal.yaml";
> > +	const std::string filePath = findFilePath(configPath);
> > +	if (filePath.empty()) {
> > +		LOG(HALConfig, Warning)
>
> This can be an Error, as we can't continue.
>
> > +			<< "File: \"" << configPath << "\" not found";
>
> Maybe s/File:/Configuration file/ ?
>
> > +		return -ENOENT;
> > +	}
> > +
> > +	FILE *fh = fopen(filePath.c_str(), "r");
> > +	if (!fh) {
> > +		ret = -errno;
> > +		LOG(HALConfig, Error) << "Failed to open file " << filePath
>
> Same here, "configuration file" ?
>
> > +				      << ": " << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	yaml_parser_set_input_file(&parser_, fh);
> > +
> > +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> > +
> > +	ret = parseConfigFile();
> > +	fclose(fh);
> > +	if (ret)
> > +		return ret;
> > +
> > +	LOG(HALConfig, Info) << "Device model: " << model_;
> > +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> > +	for (const auto &c : cameras_) {
> > +		const CameraBlock camera = c.second;
> > +		LOG(HALConfig, Info) << camera.toString();
> > +	}
>
> I wonder if dumping the parsed file shouldn't be a debugging feature
> instead, or even if we need it at all.
>
> > +
> > +	return 0;
> > +}
> > +
> > +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> > +{
> > +	static const std::string empty("");
> > +	const auto &it = cameras_.find(camera);
> > +	if (it == cameras_.end()) {
> > +		LOG(HALConfig, Error)
> > +			<< "Camera '" << camera
> > +			<< "' not described in the HAL configuration file";
> > +		return empty;
> > +	}
> > +
> > +	const CameraBlock &cam = it->second;
> > +	return cam.location;
> > +}
> > +
> > +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> > +{
> > +	static const std::string empty("");
> > +	const auto &it = cameras_.find(camera);
> > +	if (it == cameras_.end()) {
> > +		LOG(HALConfig, Error)
> > +			<< "Camera '" << camera
> > +			<< "' not described in the HAL configuration file";
> > +		return 0;
> > +	}
> > +
> > +	const CameraBlock &cam = it->second;
> > +	return cam.rotation;
> > +}
> > +
> > +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> > +{
> > +	static const std::string empty("");
> > +	const auto &it = cameras_.find(camera);
> > +	if (it == cameras_.end()) {
> > +		LOG(HALConfig, Error)
> > +			<< "Camera '" << camera
> > +			<< "' not described in the HAL configuration file";
> > +		return empty;
> > +	}
> > +
> > +	const CameraBlock &cam = it->second;
> > +	return cam.model;
> > +}
> > +
> > +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> > +{
> > +	/* Make sure the token type is a value and get its content. */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_VALUE_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_SCALAR_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +
> > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > +	std::string value(scalar, token.data.scalar.length);
> > +	yaml_token_delete(&token);
> > +
> > +	return value;
> > +}
> > +
> > +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> > +{
> > +	/* Make sure the token type is a key and get its value. */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_KEY_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_SCALAR_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return "";
> > +	}
> > +
> > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > +	std::string key(scalar, token.data.scalar.length);
> > +	yaml_token_delete(&token);
> > +
> > +	return key;
> > +}
> > +
> > +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> > +{
> > +	/* The 'camera' block is a VALUE token type. */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_VALUE_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	/*
> > +	 * Parse the content of the camera block until we have an unbalanced
> > +	 * BLOCK_END_TOKEN which closes the camera block.
> > +	 *
> > +	 * Add a safety counter to make sure we don't loop indefinitely in case
> > +	 * the configuration file is malformed.
> > +	 */
> > +	unsigned int sentinel = 100;
> > +	CameraBlock cameraBlock{};
> > +	int blockCounter = 0;
> > +	do {
> > +		yaml_parser_scan(&parser_, &token);
> > +		switch (token.type) {
> > +		case YAML_BLOCK_ENTRY_TOKEN:
> > +			yaml_token_delete(&token);
> > +			blockCounter++;
> > +			break;
> > +		case YAML_BLOCK_END_TOKEN:
> > +			yaml_token_delete(&token);
> > +			blockCounter--;
> > +			break;
> > +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> > +			yaml_token_delete(&token);
> > +
> > +			std::string key = parseKey(token);
> > +			std::string value = parseValue(token);
> > +			if (key.empty() || value.empty()) {
> > +				LOG(HALConfig, Error)
> > +					<< "Configuration file is not valid";
> > +				return -EINVAL;
> > +			}
> > +
> > +			/* Validate that the parsed key is valid. */
> > +			if (key == "location") {
> > +				if (value != "front" && value != "back" &&
> > +				    value != "external") {
> > +					LOG(HALConfig, Error)
> > +						<< "Unknown location: " << value;
> > +					return -EINVAL;
> > +				}
> > +				cameraBlock.location = value;
> > +			} else if (key == "rotation") {
> > +				cameraBlock.rotation = strtoul(value.c_str(),
> > +							       NULL, 10);
> > +				if (cameraBlock.rotation < 0) {
> > +					LOG(HALConfig, Error)
> > +						<< "Unknown rotation: "
> > +						<< cameraBlock.rotation;
> > +					return -EINVAL;
> > +				}
> > +			} else if (key == "name") {
> > +				cameraBlock.name = value;
> > +			} else if (key == "model") {
> > +				cameraBlock.model = value;
> > +			} else {
> > +				LOG(HALConfig, Error)
> > +					<< "Configuration file is not valid "
> > +					<< "unknown key: " << key;
> > +				return -EINVAL;
> > +			}
> > +			break;
> > +		}
> > +		default:
> > +			yaml_token_delete(&token);
> > +			break;
> > +		}
> > +		--sentinel;
> > +	} while (blockCounter >= 0 && sentinel);
> > +	if (!sentinel) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> > +		return -EINVAL;
> > +	}
> > +
> > +	cameras_[cameraBlock.name] = cameraBlock;
> > +
> > +	return 0;
> > +}
> > +
> > +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> > +{
> > +	int ret;
> > +
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	std::string key = parseKey(token);
> > +	if (key.empty()) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (key == "camera") {
> > +		yaml_token_delete(&token);
> > +		ret = parseCameraBlock(token);
> > +		if (ret)
> > +			return ret;
> > +	} else if (key == "manufacturer") {
> > +		yaml_token_delete(&token);
> > +		maker_ = parseValue(token);
> > +		if (maker_.empty()) {
> > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > +			return -EINVAL;
> > +		}
> > +	} else if (key == "model") {
> > +		yaml_token_delete(&token);
> > +		model_ = parseValue(token);
> > +		if (model_.empty()) {
> > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > +			return -EINVAL;
> > +		}
> > +	} else {
> > +		yaml_token_delete(&token);
> > +		LOG(HALConfig, Error) << "Unknown key " << key;
> > +		return -EINVAL;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +int CameraHalConfig::parseConfigFile()
> > +{
> > +	yaml_token_t token;
> > +	int ret;
> > +
> > +	yaml_parser_scan(&parser_, &token);
>
> Just to make sure you're aware of it, there's yaml_parser_load() that
> produces a parsed document that can then be walked. There's nothing
> wrong about using event-based parsing as you do though.

I've found that feature under-documented with very few usage examples
around.

>
> I've skipped review of the parser implementation itself, as it may be
> changed depending on how patch 7/7 evolves.
>
> > +	if (token.type != YAML_STREAM_START_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return EINVAL;
>
> -EINVAL
>
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	/*
> > +	 * Start parsing the content of the configuration file which
> > +	 * starts as with sequence block.
>
> s/as // ?
>
> > +	 */
> > +	yaml_parser_scan(&parser_, &token);
> > +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > +		return -EINVAL;
> > +	}
> > +	yaml_token_delete(&token);
> > +
> > +	/* Parse the single entry blocks. */
> > +	do {
> > +		yaml_parser_scan(&parser_, &token);
> > +		switch (token.type) {
> > +		case YAML_BLOCK_ENTRY_TOKEN:
> > +			yaml_token_delete(&token);
> > +			ret = parseEntryBlock(token);
> > +			break;
> > +		case YAML_STREAM_END_TOKEN:
> > +			/* Resources are released after the loop exit. */
> > +			break;
> > +		default:
> > +			/* Release resources before re-parsing. */
> > +			yaml_token_delete(&token);
> > +			break;
> > +		}
> > +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> > +	yaml_token_delete(&token);
> > +
> > +	return ret;
> > +}
> > diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> > new file mode 100644
> > index 000000000000..69d42658e90a
> > --- /dev/null
> > +++ b/src/android/camera_hal_config.h
> > @@ -0,0 +1,54 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Google Inc.
> > + *
> > + * camera_hal_config.h - Camera HAL configuration file manager
> > + */
> > +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> > +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> > +
> > +#include <map>
> > +#include <string>
> > +#include <yaml.h>
>
> It would be nice to hide the YAML perser from this header. It could be
> done by moving the parsing to a CameraHalConfigParser class, private to
> the camera_hal_config.cpp file.
>

.. ok

> > +
> > +class CameraHalConfig
> > +{
> > +public:
> > +	CameraHalConfig();
> > +	~CameraHalConfig();
> > +
> > +	int open();
> > +
> > +	const std::string &deviceModel() const { return model_; }
> > +	const std::string &deviceMaker() const { return maker_; }
> > +
> > +	const std::string &cameraLocation(const std::string &camera) const;
> > +	unsigned int cameraRotation(const std::string &camera) const;
> > +	const std::string &cameraModel(const std::string &camera) const;
> > +
> > +private:
> > +	yaml_parser_t parser_;
> > +
> > +	std::string model_;
> > +	std::string maker_;
> > +	class CameraBlock
> > +	{
> > +	public:
> > +		std::string name;
> > +		std::string location;
> > +		std::string model;
> > +		unsigned int rotation;
> > +
> > +		const std::string toString() const;
> > +	};
>
> How about making this class public (and probably renaming it), and replacing the last three public
> functions with
>
> 	const CameraBlock *cameraConfig(const std::string &name) const;
>
> ? That way each CameraDevice could store a reference to the
> corresponding camera configuration only.
>

I prefer the CameraHalConfig::cameraProperty() interface but ok

> > +	std::map<std::string, CameraBlock> cameras_;
> > +
> > +	const std::string findFilePath(const std::string &filename);
>
> No need for const at the beginning as you return by value, but you can
> add a const at the end.
>
> > +	std::string parseValue(yaml_token_t &token);
> > +	std::string parseKey(yaml_token_t &token);
> > +	int parseCameraBlock(yaml_token_t &token);
> > +	int parseEntryBlock(yaml_token_t &token);
> > +	int parseConfigFile();
> > +};
> > +
> > +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> > diff --git a/src/android/meson.build b/src/android/meson.build
> > index 19f94a4073f1..34e5c494a492 100644
> > --- a/src/android/meson.build
> > +++ b/src/android/meson.build
> > @@ -44,6 +44,7 @@ android_hal_sources = files([
> >      'camera3_hal.cpp',
> >      'camera_hal_manager.cpp',
> >      'camera_device.cpp',
> > +    'camera_hal_config.cpp',
> >      'camera_metadata.cpp',
> >      'camera_ops.cpp',
> >      'camera_stream.cpp',
>
> --
> Regards,
>
> Laurent Pinchart
Jacopo Mondi March 29, 2021, 2:43 p.m. UTC | #6
On Mon, Mar 29, 2021 at 04:27:01PM +0200, Niklas Söderlund wrote:
> Hi Jacopo,
>
> On 2021-03-29 16:23:08 +0200, Jacopo Mondi wrote:
> > Hi Niklas,
> >
> > On Thu, Mar 25, 2021 at 11:33:31PM +0100, Niklas Söderlund wrote:
> > > Hi Jacopo,
> > >
> > > Thanks for your work.
> > >
> > > On 2021-03-24 12:25:24 +0100, Jacopo Mondi wrote:
> > > > Add a CameraHalConfig class to the Android Camera3 HAL layer.
> > > >
> > > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > >
> > > There are a few small nits below but I think this is a great step in the
> > > right direction.
> > >
> > > Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> > >
> > > > ---
> > > >  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
> > > >  src/android/camera_hal_config.h   |  54 +++++
> > > >  src/android/meson.build           |   1 +
> > > >  3 files changed, 440 insertions(+)
> > > >  create mode 100644 src/android/camera_hal_config.cpp
> > > >  create mode 100644 src/android/camera_hal_config.h
> > > >
> > > > diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> > > > new file mode 100644
> > > > index 000000000000..7e33dfb750ff
> > > > --- /dev/null
> > > > +++ b/src/android/camera_hal_config.cpp
> > > > @@ -0,0 +1,385 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2021, Google Inc.
> > > > + *
> > > > + * camera_hal_config.cpp - Camera HAL configuration file manager
> > > > + */
> > > > +#include "camera_hal_config.h"
> > > > +
> > > > +#include <stdio.h>
> > > > +#include <stdlib.h>
> > > > +#include <string.h>
> > > > +
> > > > +#include "libcamera/internal/file.h"
> > > > +#include "libcamera/internal/log.h"
> > > > +
> > > > +using namespace libcamera;
> > > > +
> > > > +LOG_DEFINE_CATEGORY(HALConfig)
> > > > +
> > > > +const std::string CameraHalConfig::CameraBlock::toString() const
> > > > +{
> > > > +	std::stringstream ss;
> > > > +
> > > > +	ss << "\'" << name << "\'"
> > > > +	   << " model: " << model
> > > > +	   << "(" << location << ")[" << rotation << "]";
> > >
> > > nit: The usage of the pattern "key: <value>" have been discouraged in
> > > the core, do we extend the same to the HAL?
> > >
> >
> > Not sure what you mean here -.-
>
> Sorry, I just noticed that the pattern of
>
>     << " model: " << model
>
> Have been discouraged in the core in favor of
>
>     << " model " << model
>

Ah ok, we're talking about the ":" in an Info debug message.
Sorry I missed such change in the core.

I'll fix

> >
> > > > +
> > > > +	return ss.str();
> > > > +}
> > > > +
> > > > +CameraHalConfig::CameraHalConfig()
> > > > +{
> > > > +	if (!yaml_parser_initialize(&parser_))
> > > > +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
> > >
> > > Fatal?
> > >
> > > > +}
> > > > +
> > > > +CameraHalConfig::~CameraHalConfig()
> > > > +{
> > > > +	yaml_parser_delete(&parser_);
> > > > +}
> > > > +
> > > > +/*
> > > > + * Configuration files can be stored in system paths, which are identified
> > > > + * through the build configuration.
> > > > + *
> > > > + * However, when running uninstalled - the source location takes precedence.
> > > > + */
> > > > +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> > > > +{
> > > > +	static std::array<std::string, 2> searchPaths = {
> > > > +		LIBCAMERA_SYSCONF_DIR,
> > > > +		LIBCAMERA_DATA_DIR,
> > > > +	};
> > > > +
> > > > +	if (File::exists(filename))
> > > > +		return filename;
> > > > +
> > > > +	std::string root = utils::libcameraSourcePath();
> > > > +	if (!root.empty()) {
> > > > +		std::string configurationPath = root + "data/" + filename;
> > > > +
> > > > +		if (File::exists(configurationPath))
> > > > +			return configurationPath;
> > > > +	}
> > > > +
> > > > +	for (std::string &path : searchPaths) {
> > > > +		std::string configurationPath = path + "/" + filename;
> > > > +		if (File::exists(configurationPath))
> > > > +			return configurationPath;
> > > > +	}
> > > > +
> > > > +	return "";
> > > > +}
> > > > +
> > > > +/*
> > > > + * \brief Open the HAL configuration file and validate its content
> > > > + * \return 0 on success, a negative error code otherwise
> > >
> > > nit: We don't Doxygen the HAL is it not confusing to use it mark up here
> > > then?
> > >
> > > > + */
> > > > +int CameraHalConfig::open()
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	const std::string configPath = "camera_hal.yaml";
> > >
> > > s/configPath/configFile/ ?
> > >
> > > > +	const std::string filePath = findFilePath(configPath);
> > >
> > > As this is the only call site would it make sens to have findFilePath()
> > > return a FILE * to get compartmentalizing of the error paths?
> > >
> > > > +	if (filePath.empty()) {
> > > > +		LOG(HALConfig, Warning)
> > > > +			<< "File: \"" << configPath << "\" not found";
> > > > +		return -ENOENT;
> > > > +	}
> > > > +
> > > > +	FILE *fh = fopen(filePath.c_str(), "r");
> > > > +	if (!fh) {
> > > > +		ret = -errno;
> > > > +		LOG(HALConfig, Error) << "Failed to open file " << filePath
> > > > +				      << ": " << strerror(-ret);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	yaml_parser_set_input_file(&parser_, fh);
> > > > +
> > > > +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> > > > +
> > > > +	ret = parseConfigFile();
> > > > +	fclose(fh);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	LOG(HALConfig, Info) << "Device model: " << model_;
> > > > +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> > > > +	for (const auto &c : cameras_) {
> > > > +		const CameraBlock camera = c.second;
> > > > +		LOG(HALConfig, Info) << camera.toString();
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> > > > +{
> > > > +	static const std::string empty("");
> > > > +	const auto &it = cameras_.find(camera);
> > > > +	if (it == cameras_.end()) {
> > > > +		LOG(HALConfig, Error)
> > > > +			<< "Camera '" << camera
> > > > +			<< "' not described in the HAL configuration file";
> > > > +		return empty;
> > > > +	}
> > > > +
> > > > +	const CameraBlock &cam = it->second;
> > > > +	return cam.location;
> > > > +}
> > > > +
> > > > +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> > > > +{
> > > > +	static const std::string empty("");
> > >
> > > empty not used.
> > >
> >
> > Ouch, leftover
> >
> > > > +	const auto &it = cameras_.find(camera);
> > > > +	if (it == cameras_.end()) {
> > > > +		LOG(HALConfig, Error)
> > > > +			<< "Camera '" << camera
> > > > +			<< "' not described in the HAL configuration file";
> > > > +		return 0;
> > > > +	}
> > > > +
> > > > +	const CameraBlock &cam = it->second;
> > > > +	return cam.rotation;
> > > > +}
> > > > +
> > > > +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> > > > +{
> > > > +	static const std::string empty("");
> > > > +	const auto &it = cameras_.find(camera);
> > > > +	if (it == cameras_.end()) {
> > > > +		LOG(HALConfig, Error)
> > > > +			<< "Camera '" << camera
> > > > +			<< "' not described in the HAL configuration file";
> > > > +		return empty;
> > > > +	}
> > > > +
> > > > +	const CameraBlock &cam = it->second;
> > > > +	return cam.model;
> > > > +}
> > > > +
> > > > +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> > > > +{
> > > > +	/* Make sure the token type is a value and get its content. */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +
> > > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > > +	std::string value(scalar, token.data.scalar.length);
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	return value;
> > > > +}
> > > > +
> > > > +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> > > > +{
> > > > +	/* Make sure the token type is a key and get its value. */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_KEY_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +
> > > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > > +	std::string key(scalar, token.data.scalar.length);
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	return key;
> > > > +}
> > > > +
> > > > +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> > > > +{
> > > > +	/* The 'camera' block is a VALUE token type. */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	/*
> > > > +	 * Parse the content of the camera block until we have an unbalanced
> > > > +	 * BLOCK_END_TOKEN which closes the camera block.
> > > > +	 *
> > > > +	 * Add a safety counter to make sure we don't loop indefinitely in case
> > > > +	 * the configuration file is malformed.
> > > > +	 */
> > > > +	unsigned int sentinel = 100;
> > > > +	CameraBlock cameraBlock{};
> > > > +	int blockCounter = 0;
> > > > +	do {
> > > > +		yaml_parser_scan(&parser_, &token);
> > > > +		switch (token.type) {
> > > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > > +			yaml_token_delete(&token);
> > > > +			blockCounter++;
> > > > +			break;
> > > > +		case YAML_BLOCK_END_TOKEN:
> > > > +			yaml_token_delete(&token);
> > > > +			blockCounter--;
> > > > +			break;
> > > > +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> > > > +			yaml_token_delete(&token);
> > > > +
> > > > +			std::string key = parseKey(token);
> > > > +			std::string value = parseValue(token);
> > > > +			if (key.empty() || value.empty()) {
> > > > +				LOG(HALConfig, Error)
> > > > +					<< "Configuration file is not valid";
> > > > +				return -EINVAL;
> > > > +			}
> > > > +
> > > > +			/* Validate that the parsed key is valid. */
> > > > +			if (key == "location") {
> > > > +				if (value != "front" && value != "back" &&
> > > > +				    value != "external") {
> > > > +					LOG(HALConfig, Error)
> > > > +						<< "Unknown location: " << value;
> > > > +					return -EINVAL;
> > > > +				}
> > > > +				cameraBlock.location = value;
> > > > +			} else if (key == "rotation") {
> > > > +				cameraBlock.rotation = strtoul(value.c_str(),
> > > > +							       NULL, 10);
> > > > +				if (cameraBlock.rotation < 0) {
> > > > +					LOG(HALConfig, Error)
> > > > +						<< "Unknown rotation: "
> > > > +						<< cameraBlock.rotation;
> > > > +					return -EINVAL;
> > > > +				}
> > > > +			} else if (key == "name") {
> > > > +				cameraBlock.name = value;
> > > > +			} else if (key == "model") {
> > > > +				cameraBlock.model = value;
> > > > +			} else {
> > > > +				LOG(HALConfig, Error)
> > > > +					<< "Configuration file is not valid "
> > > > +					<< "unknown key: " << key;
> > > > +				return -EINVAL;
> > > > +			}
> > > > +			break;
> > > > +		}
> > > > +		default:
> > > > +			yaml_token_delete(&token);
> > > > +			break;
> > > > +		}
> > > > +		--sentinel;
> > > > +	} while (blockCounter >= 0 && sentinel);
> > > > +	if (!sentinel) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	cameras_[cameraBlock.name] = cameraBlock;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	std::string key = parseKey(token);
> > > > +	if (key.empty()) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	if (key == "camera") {
> > > > +		yaml_token_delete(&token);
> > > > +		ret = parseCameraBlock(token);
> > > > +		if (ret)
> > > > +			return ret;
> > > > +	} else if (key == "manufacturer") {
> > > > +		yaml_token_delete(&token);
> > > > +		maker_ = parseValue(token);
> > > > +		if (maker_.empty()) {
> > > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +			return -EINVAL;
> > > > +		}
> > > > +	} else if (key == "model") {
> > > > +		yaml_token_delete(&token);
> > > > +		model_ = parseValue(token);
> > > > +		if (model_.empty()) {
> > > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +			return -EINVAL;
> > > > +		}
> > > > +	} else {
> > > > +		yaml_token_delete(&token);
> > > > +		LOG(HALConfig, Error) << "Unknown key " << key;
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +int CameraHalConfig::parseConfigFile()
> > > > +{
> > > > +	yaml_token_t token;
> > > > +	int ret;
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_STREAM_START_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	/*
> > > > +	 * Start parsing the content of the configuration file which
> > > > +	 * starts as with sequence block.
> > > > +	 */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	/* Parse the single entry blocks. */
> > > > +	do {
> > > > +		yaml_parser_scan(&parser_, &token);
> > > > +		switch (token.type) {
> > > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > > +			yaml_token_delete(&token);
> > > > +			ret = parseEntryBlock(token);
> > > > +			break;
> > > > +		case YAML_STREAM_END_TOKEN:
> > > > +			/* Resources are released after the loop exit. */
> > > > +			break;
> > > > +		default:
> > > > +			/* Release resources before re-parsing. */
> > > > +			yaml_token_delete(&token);
> > > > +			break;
> > > > +		}
> > > > +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> > > > new file mode 100644
> > > > index 000000000000..69d42658e90a
> > > > --- /dev/null
> > > > +++ b/src/android/camera_hal_config.h
> > > > @@ -0,0 +1,54 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2021, Google Inc.
> > > > + *
> > > > + * camera_hal_config.h - Camera HAL configuration file manager
> > > > + */
> > > > +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> > > > +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> > > > +
> > > > +#include <map>
> > > > +#include <string>
> > > > +#include <yaml.h>
> > > > +
> > > > +class CameraHalConfig
> > > > +{
> > > > +public:
> > > > +	CameraHalConfig();
> > > > +	~CameraHalConfig();
> > > > +
> > > > +	int open();
> > > > +
> > > > +	const std::string &deviceModel() const { return model_; }
> > > > +	const std::string &deviceMaker() const { return maker_; }
> > > > +
> > > > +	const std::string &cameraLocation(const std::string &camera) const;
> > > > +	unsigned int cameraRotation(const std::string &camera) const;
> > > > +	const std::string &cameraModel(const std::string &camera) const;
> > > > +
> > > > +private:
> > > > +	yaml_parser_t parser_;
> > > > +
> > > > +	std::string model_;
> > > > +	std::string maker_;
> > > > +	class CameraBlock
> > > > +	{
> > > > +	public:
> > > > +		std::string name;
> > > > +		std::string location;
> > > > +		std::string model;
> > > > +		unsigned int rotation;
> > > > +
> > > > +		const std::string toString() const;
> > > > +	};
> > > > +	std::map<std::string, CameraBlock> cameras_;
> > > > +
> > > > +	const std::string findFilePath(const std::string &filename);
> > > > +	std::string parseValue(yaml_token_t &token);
> > > > +	std::string parseKey(yaml_token_t &token);
> > > > +	int parseCameraBlock(yaml_token_t &token);
> > > > +	int parseEntryBlock(yaml_token_t &token);
> > > > +	int parseConfigFile();
> > > > +};
> > > > +
> > > > +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> > > > diff --git a/src/android/meson.build b/src/android/meson.build
> > > > index 19f94a4073f1..34e5c494a492 100644
> > > > --- a/src/android/meson.build
> > > > +++ b/src/android/meson.build
> > > > @@ -44,6 +44,7 @@ android_hal_sources = files([
> > > >      'camera3_hal.cpp',
> > > >      'camera_hal_manager.cpp',
> > > >      'camera_device.cpp',
> > > > +    'camera_hal_config.cpp',
> > > >      'camera_metadata.cpp',
> > > >      'camera_ops.cpp',
> > > >      'camera_stream.cpp',
> > > > --
> > > > 2.30.0
> > > >
> > > > _______________________________________________
> > > > libcamera-devel mailing list
> > > > libcamera-devel@lists.libcamera.org
> > > > https://lists.libcamera.org/listinfo/libcamera-devel
> > >
> > > --
> > > Regards,
> > > Niklas Söderlund
>
> --
> Regards,
> Niklas Söderlund
Laurent Pinchart March 29, 2021, 5:43 p.m. UTC | #7
Hi Jacopo,

On Mon, Mar 29, 2021 at 04:27:22PM +0200, Jacopo Mondi wrote:
> On Fri, Mar 26, 2021 at 06:07:07AM +0200, Laurent Pinchart wrote:
> > On Wed, Mar 24, 2021 at 12:25:24PM +0100, Jacopo Mondi wrote:
> > > Add a CameraHalConfig class to the Android Camera3 HAL layer.
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > > ---
> > >  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
> > >  src/android/camera_hal_config.h   |  54 +++++
> > >  src/android/meson.build           |   1 +
> > >  3 files changed, 440 insertions(+)
> > >  create mode 100644 src/android/camera_hal_config.cpp
> > >  create mode 100644 src/android/camera_hal_config.h
> > >
> > > diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> > > new file mode 100644
> > > index 000000000000..7e33dfb750ff
> > > --- /dev/null
> > > +++ b/src/android/camera_hal_config.cpp
> > > @@ -0,0 +1,385 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Google Inc.
> > > + *
> > > + * camera_hal_config.cpp - Camera HAL configuration file manager
> > > + */
> > > +#include "camera_hal_config.h"
> > > +
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +
> > > +#include "libcamera/internal/file.h"
> > > +#include "libcamera/internal/log.h"
> > > +
> > > +using namespace libcamera;
> > > +
> > > +LOG_DEFINE_CATEGORY(HALConfig)
> > > +
> > > +const std::string CameraHalConfig::CameraBlock::toString() const
> > > +{
> > > +	std::stringstream ss;
> > > +
> > > +	ss << "\'" << name << "\'"
> > > +	   << " model: " << model
> > > +	   << "(" << location << ")[" << rotation << "]";
> > > +
> > > +	return ss.str();
> > > +}
> >
> > As the amount of information will grow, I don't think we'll be able to
> > print everything. This function is only used in a single location below,
> > for debugging purpose, I wonder if we should keep it.
> >
> > > +
> > > +CameraHalConfig::CameraHalConfig()
> > > +{
> > > +	if (!yaml_parser_initialize(&parser_))
> > > +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
> > > +}
> > > +
> > > +CameraHalConfig::~CameraHalConfig()
> > > +{
> > > +	yaml_parser_delete(&parser_);
> > > +}
> > > +
> > > +/*
> > > + * Configuration files can be stored in system paths, which are identified
> > > + * through the build configuration.
> > > + *
> > > + * However, when running uninstalled - the source location takes precedence.
> >
> > Can we run the camera HAL uninstalled ? :-)
> >
> 
> This meant "libcamera" uninstalled.

Yes, but as this is the HAL configuration, I don't think we need to care
about the uninstalled case. But now that I think about it, maybe you
meant for this function to be ready for the libcamera core configuration
file too ?

> > > + */
> > > +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> > > +{
> > > +	static std::array<std::string, 2> searchPaths = {
> > > +		LIBCAMERA_SYSCONF_DIR,
> > > +		LIBCAMERA_DATA_DIR,
> >
> > The data dir may not be very useful here, the configuration file is
> > really system-specific, right ?
> >
> > I expect we may need to get the configuration file from system-specific
> > locations, depending on whether we run on Chrome OS or Android, but we
> > can handle that later.
> >
> > > +	};
> > > +
> > > +	if (File::exists(filename))
> > > +		return filename;
> > > +
> > > +	std::string root = utils::libcameraSourcePath();
> > > +	if (!root.empty()) {
> > > +		std::string configurationPath = root + "data/" + filename;
> > > +
> > > +		if (File::exists(configurationPath))
> > > +			return configurationPath;
> > > +	}
> > > +
> > > +	for (std::string &path : searchPaths) {
> > > +		std::string configurationPath = path + "/" + filename;
> > > +		if (File::exists(configurationPath))
> > > +			return configurationPath;
> > > +	}
> > > +
> > > +	return "";
> > > +}
> > > +
> > > +/*
> > > + * \brief Open the HAL configuration file and validate its content
> > > + * \return 0 on success, a negative error code otherwise
> > > + */
> > > +int CameraHalConfig::open()
> > > +{
> > > +	int ret;
> > > +
> > > +	const std::string configPath = "camera_hal.yaml";
> > > +	const std::string filePath = findFilePath(configPath);
> > > +	if (filePath.empty()) {
> > > +		LOG(HALConfig, Warning)
> >
> > This can be an Error, as we can't continue.
> >
> > > +			<< "File: \"" << configPath << "\" not found";
> >
> > Maybe s/File:/Configuration file/ ?
> >
> > > +		return -ENOENT;
> > > +	}
> > > +
> > > +	FILE *fh = fopen(filePath.c_str(), "r");
> > > +	if (!fh) {
> > > +		ret = -errno;
> > > +		LOG(HALConfig, Error) << "Failed to open file " << filePath
> >
> > Same here, "configuration file" ?
> >
> > > +				      << ": " << strerror(-ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	yaml_parser_set_input_file(&parser_, fh);
> > > +
> > > +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> > > +
> > > +	ret = parseConfigFile();
> > > +	fclose(fh);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	LOG(HALConfig, Info) << "Device model: " << model_;
> > > +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> > > +	for (const auto &c : cameras_) {
> > > +		const CameraBlock camera = c.second;
> > > +		LOG(HALConfig, Info) << camera.toString();
> > > +	}
> >
> > I wonder if dumping the parsed file shouldn't be a debugging feature
> > instead, or even if we need it at all.
> >
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> > > +{
> > > +	static const std::string empty("");
> > > +	const auto &it = cameras_.find(camera);
> > > +	if (it == cameras_.end()) {
> > > +		LOG(HALConfig, Error)
> > > +			<< "Camera '" << camera
> > > +			<< "' not described in the HAL configuration file";
> > > +		return empty;
> > > +	}
> > > +
> > > +	const CameraBlock &cam = it->second;
> > > +	return cam.location;
> > > +}
> > > +
> > > +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> > > +{
> > > +	static const std::string empty("");
> > > +	const auto &it = cameras_.find(camera);
> > > +	if (it == cameras_.end()) {
> > > +		LOG(HALConfig, Error)
> > > +			<< "Camera '" << camera
> > > +			<< "' not described in the HAL configuration file";
> > > +		return 0;
> > > +	}
> > > +
> > > +	const CameraBlock &cam = it->second;
> > > +	return cam.rotation;
> > > +}
> > > +
> > > +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> > > +{
> > > +	static const std::string empty("");
> > > +	const auto &it = cameras_.find(camera);
> > > +	if (it == cameras_.end()) {
> > > +		LOG(HALConfig, Error)
> > > +			<< "Camera '" << camera
> > > +			<< "' not described in the HAL configuration file";
> > > +		return empty;
> > > +	}
> > > +
> > > +	const CameraBlock &cam = it->second;
> > > +	return cam.model;
> > > +}
> > > +
> > > +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> > > +{
> > > +	/* Make sure the token type is a value and get its content. */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +
> > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > +	std::string value(scalar, token.data.scalar.length);
> > > +	yaml_token_delete(&token);
> > > +
> > > +	return value;
> > > +}
> > > +
> > > +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> > > +{
> > > +	/* Make sure the token type is a key and get its value. */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_KEY_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return "";
> > > +	}
> > > +
> > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > +	std::string key(scalar, token.data.scalar.length);
> > > +	yaml_token_delete(&token);
> > > +
> > > +	return key;
> > > +}
> > > +
> > > +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> > > +{
> > > +	/* The 'camera' block is a VALUE token type. */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	/*
> > > +	 * Parse the content of the camera block until we have an unbalanced
> > > +	 * BLOCK_END_TOKEN which closes the camera block.
> > > +	 *
> > > +	 * Add a safety counter to make sure we don't loop indefinitely in case
> > > +	 * the configuration file is malformed.
> > > +	 */
> > > +	unsigned int sentinel = 100;
> > > +	CameraBlock cameraBlock{};
> > > +	int blockCounter = 0;
> > > +	do {
> > > +		yaml_parser_scan(&parser_, &token);
> > > +		switch (token.type) {
> > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > +			yaml_token_delete(&token);
> > > +			blockCounter++;
> > > +			break;
> > > +		case YAML_BLOCK_END_TOKEN:
> > > +			yaml_token_delete(&token);
> > > +			blockCounter--;
> > > +			break;
> > > +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> > > +			yaml_token_delete(&token);
> > > +
> > > +			std::string key = parseKey(token);
> > > +			std::string value = parseValue(token);
> > > +			if (key.empty() || value.empty()) {
> > > +				LOG(HALConfig, Error)
> > > +					<< "Configuration file is not valid";
> > > +				return -EINVAL;
> > > +			}
> > > +
> > > +			/* Validate that the parsed key is valid. */
> > > +			if (key == "location") {
> > > +				if (value != "front" && value != "back" &&
> > > +				    value != "external") {
> > > +					LOG(HALConfig, Error)
> > > +						<< "Unknown location: " << value;
> > > +					return -EINVAL;
> > > +				}
> > > +				cameraBlock.location = value;
> > > +			} else if (key == "rotation") {
> > > +				cameraBlock.rotation = strtoul(value.c_str(),
> > > +							       NULL, 10);
> > > +				if (cameraBlock.rotation < 0) {
> > > +					LOG(HALConfig, Error)
> > > +						<< "Unknown rotation: "
> > > +						<< cameraBlock.rotation;
> > > +					return -EINVAL;
> > > +				}
> > > +			} else if (key == "name") {
> > > +				cameraBlock.name = value;
> > > +			} else if (key == "model") {
> > > +				cameraBlock.model = value;
> > > +			} else {
> > > +				LOG(HALConfig, Error)
> > > +					<< "Configuration file is not valid "
> > > +					<< "unknown key: " << key;
> > > +				return -EINVAL;
> > > +			}
> > > +			break;
> > > +		}
> > > +		default:
> > > +			yaml_token_delete(&token);
> > > +			break;
> > > +		}
> > > +		--sentinel;
> > > +	} while (blockCounter >= 0 && sentinel);
> > > +	if (!sentinel) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	cameras_[cameraBlock.name] = cameraBlock;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> > > +{
> > > +	int ret;
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	std::string key = parseKey(token);
> > > +	if (key.empty()) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	if (key == "camera") {
> > > +		yaml_token_delete(&token);
> > > +		ret = parseCameraBlock(token);
> > > +		if (ret)
> > > +			return ret;
> > > +	} else if (key == "manufacturer") {
> > > +		yaml_token_delete(&token);
> > > +		maker_ = parseValue(token);
> > > +		if (maker_.empty()) {
> > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +			return -EINVAL;
> > > +		}
> > > +	} else if (key == "model") {
> > > +		yaml_token_delete(&token);
> > > +		model_ = parseValue(token);
> > > +		if (model_.empty()) {
> > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +			return -EINVAL;
> > > +		}
> > > +	} else {
> > > +		yaml_token_delete(&token);
> > > +		LOG(HALConfig, Error) << "Unknown key " << key;
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +int CameraHalConfig::parseConfigFile()
> > > +{
> > > +	yaml_token_t token;
> > > +	int ret;
> > > +
> > > +	yaml_parser_scan(&parser_, &token);
> >
> > Just to make sure you're aware of it, there's yaml_parser_load() that
> > produces a parsed document that can then be walked. There's nothing
> > wrong about using event-based parsing as you do though.
> 
> I've found that feature under-documented with very few usage examples
> around.

Agreed, it's underdocumented in libyaml :-( I've found
http://codepad.org/W7StVSkV that shows how to walk a document. If you
think that would be simpler to use, feel free to do so, otherwise the
token-based parser can be kept.

> > I've skipped review of the parser implementation itself, as it may be
> > changed depending on how patch 7/7 evolves.
> >
> > > +	if (token.type != YAML_STREAM_START_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return EINVAL;
> >
> > -EINVAL
> >
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	/*
> > > +	 * Start parsing the content of the configuration file which
> > > +	 * starts as with sequence block.
> >
> > s/as // ?
> >
> > > +	 */
> > > +	yaml_parser_scan(&parser_, &token);
> > > +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > +		return -EINVAL;
> > > +	}
> > > +	yaml_token_delete(&token);
> > > +
> > > +	/* Parse the single entry blocks. */
> > > +	do {
> > > +		yaml_parser_scan(&parser_, &token);
> > > +		switch (token.type) {
> > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > +			yaml_token_delete(&token);
> > > +			ret = parseEntryBlock(token);
> > > +			break;
> > > +		case YAML_STREAM_END_TOKEN:
> > > +			/* Resources are released after the loop exit. */
> > > +			break;
> > > +		default:
> > > +			/* Release resources before re-parsing. */
> > > +			yaml_token_delete(&token);
> > > +			break;
> > > +		}
> > > +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> > > +	yaml_token_delete(&token);
> > > +
> > > +	return ret;
> > > +}
> > > diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> > > new file mode 100644
> > > index 000000000000..69d42658e90a
> > > --- /dev/null
> > > +++ b/src/android/camera_hal_config.h
> > > @@ -0,0 +1,54 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2021, Google Inc.
> > > + *
> > > + * camera_hal_config.h - Camera HAL configuration file manager
> > > + */
> > > +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> > > +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> > > +
> > > +#include <map>
> > > +#include <string>
> > > +#include <yaml.h>
> >
> > It would be nice to hide the YAML perser from this header. It could be
> > done by moving the parsing to a CameraHalConfigParser class, private to
> > the camera_hal_config.cpp file.
> 
> .. ok
> 
> > > +
> > > +class CameraHalConfig
> > > +{
> > > +public:
> > > +	CameraHalConfig();
> > > +	~CameraHalConfig();
> > > +
> > > +	int open();
> > > +
> > > +	const std::string &deviceModel() const { return model_; }
> > > +	const std::string &deviceMaker() const { return maker_; }
> > > +
> > > +	const std::string &cameraLocation(const std::string &camera) const;
> > > +	unsigned int cameraRotation(const std::string &camera) const;
> > > +	const std::string &cameraModel(const std::string &camera) const;
> > > +
> > > +private:
> > > +	yaml_parser_t parser_;
> > > +
> > > +	std::string model_;
> > > +	std::string maker_;
> > > +	class CameraBlock
> > > +	{
> > > +	public:
> > > +		std::string name;
> > > +		std::string location;
> > > +		std::string model;
> > > +		unsigned int rotation;
> > > +
> > > +		const std::string toString() const;
> > > +	};
> >
> > How about making this class public (and probably renaming it), and replacing the last three public
> > functions with
> >
> > 	const CameraBlock *cameraConfig(const std::string &name) const;
> >
> > ? That way each CameraDevice could store a reference to the
> > corresponding camera configuration only.
> 
> I prefer the CameraHalConfig::cameraProperty() interface but ok

I wouldn't be surprised if we had to create a deeper hierarchy of
properties in YAML, in which case cameraProperty() wouldn't scale well
(there's also the lookup every time a property is accessed that we could
save).

> > > +	std::map<std::string, CameraBlock> cameras_;
> > > +
> > > +	const std::string findFilePath(const std::string &filename);
> >
> > No need for const at the beginning as you return by value, but you can
> > add a const at the end.
> >
> > > +	std::string parseValue(yaml_token_t &token);
> > > +	std::string parseKey(yaml_token_t &token);
> > > +	int parseCameraBlock(yaml_token_t &token);
> > > +	int parseEntryBlock(yaml_token_t &token);
> > > +	int parseConfigFile();
> > > +};
> > > +
> > > +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> > > diff --git a/src/android/meson.build b/src/android/meson.build
> > > index 19f94a4073f1..34e5c494a492 100644
> > > --- a/src/android/meson.build
> > > +++ b/src/android/meson.build
> > > @@ -44,6 +44,7 @@ android_hal_sources = files([
> > >      'camera3_hal.cpp',
> > >      'camera_hal_manager.cpp',
> > >      'camera_device.cpp',
> > > +    'camera_hal_config.cpp',
> > >      'camera_metadata.cpp',
> > >      'camera_ops.cpp',
> > >      'camera_stream.cpp',
Laurent Pinchart March 29, 2021, 6:10 p.m. UTC | #8
Hi Niklas,

On Mon, Mar 29, 2021 at 04:27:01PM +0200, Niklas Söderlund wrote:
> On 2021-03-29 16:23:08 +0200, Jacopo Mondi wrote:
> > On Thu, Mar 25, 2021 at 11:33:31PM +0100, Niklas Söderlund wrote:
> > > On 2021-03-24 12:25:24 +0100, Jacopo Mondi wrote:
> > > > Add a CameraHalConfig class to the Android Camera3 HAL layer.
> > > >
> > > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > >
> > > There are a few small nits below but I think this is a great step in the
> > > right direction.
> > >
> > > Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> > >
> > > > ---
> > > >  src/android/camera_hal_config.cpp | 385 ++++++++++++++++++++++++++++++
> > > >  src/android/camera_hal_config.h   |  54 +++++
> > > >  src/android/meson.build           |   1 +
> > > >  3 files changed, 440 insertions(+)
> > > >  create mode 100644 src/android/camera_hal_config.cpp
> > > >  create mode 100644 src/android/camera_hal_config.h
> > > >
> > > > diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
> > > > new file mode 100644
> > > > index 000000000000..7e33dfb750ff
> > > > --- /dev/null
> > > > +++ b/src/android/camera_hal_config.cpp
> > > > @@ -0,0 +1,385 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2021, Google Inc.
> > > > + *
> > > > + * camera_hal_config.cpp - Camera HAL configuration file manager
> > > > + */
> > > > +#include "camera_hal_config.h"
> > > > +
> > > > +#include <stdio.h>
> > > > +#include <stdlib.h>
> > > > +#include <string.h>
> > > > +
> > > > +#include "libcamera/internal/file.h"
> > > > +#include "libcamera/internal/log.h"
> > > > +
> > > > +using namespace libcamera;
> > > > +
> > > > +LOG_DEFINE_CATEGORY(HALConfig)
> > > > +
> > > > +const std::string CameraHalConfig::CameraBlock::toString() const
> > > > +{
> > > > +	std::stringstream ss;
> > > > +
> > > > +	ss << "\'" << name << "\'"
> > > > +	   << " model: " << model
> > > > +	   << "(" << location << ")[" << rotation << "]";
> > >
> > > nit: The usage of the pattern "key: <value>" have been discouraged in
> > > the core, do we extend the same to the HAL?
> > >
> > 
> > Not sure what you mean here -.-
> 
> Sorry, I just noticed that the pattern of
> 
>     << " model: " << model 
> 
> Have been discouraged in the core in favor of
> 
>     << " model " << model 

As I suppose this comes from the fact I've asked to remove colons
multiple times during review, I'd like to clarify that there's nothing
wrong with the "key: value" syntax per se. When I asked for the colon to
be dropped, that was because the message was written as a sentence. For
instance, "The camera is rotated by 90 degrees" is more natural language
than "The camera is rotated by: 90 degrees". I'd have no problem with a
message such as "configuration: width: 1280, height: 960, format: NV12".

> > > > +
> > > > +	return ss.str();
> > > > +}
> > > > +
> > > > +CameraHalConfig::CameraHalConfig()
> > > > +{
> > > > +	if (!yaml_parser_initialize(&parser_))
> > > > +		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
> > >
> > > Fatal?
> > >
> > > > +}
> > > > +
> > > > +CameraHalConfig::~CameraHalConfig()
> > > > +{
> > > > +	yaml_parser_delete(&parser_);
> > > > +}
> > > > +
> > > > +/*
> > > > + * Configuration files can be stored in system paths, which are identified
> > > > + * through the build configuration.
> > > > + *
> > > > + * However, when running uninstalled - the source location takes precedence.
> > > > + */
> > > > +const std::string CameraHalConfig::findFilePath(const std::string &filename)
> > > > +{
> > > > +	static std::array<std::string, 2> searchPaths = {
> > > > +		LIBCAMERA_SYSCONF_DIR,
> > > > +		LIBCAMERA_DATA_DIR,
> > > > +	};
> > > > +
> > > > +	if (File::exists(filename))
> > > > +		return filename;
> > > > +
> > > > +	std::string root = utils::libcameraSourcePath();
> > > > +	if (!root.empty()) {
> > > > +		std::string configurationPath = root + "data/" + filename;
> > > > +
> > > > +		if (File::exists(configurationPath))
> > > > +			return configurationPath;
> > > > +	}
> > > > +
> > > > +	for (std::string &path : searchPaths) {
> > > > +		std::string configurationPath = path + "/" + filename;
> > > > +		if (File::exists(configurationPath))
> > > > +			return configurationPath;
> > > > +	}
> > > > +
> > > > +	return "";
> > > > +}
> > > > +
> > > > +/*
> > > > + * \brief Open the HAL configuration file and validate its content
> > > > + * \return 0 on success, a negative error code otherwise
> > >
> > > nit: We don't Doxygen the HAL is it not confusing to use it mark up here
> > > then?
> > >
> > > > + */
> > > > +int CameraHalConfig::open()
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	const std::string configPath = "camera_hal.yaml";
> > >
> > > s/configPath/configFile/ ?
> > >
> > > > +	const std::string filePath = findFilePath(configPath);
> > >
> > > As this is the only call site would it make sens to have findFilePath()
> > > return a FILE * to get compartmentalizing of the error paths?
> > >
> > > > +	if (filePath.empty()) {
> > > > +		LOG(HALConfig, Warning)
> > > > +			<< "File: \"" << configPath << "\" not found";
> > > > +		return -ENOENT;
> > > > +	}
> > > > +
> > > > +	FILE *fh = fopen(filePath.c_str(), "r");
> > > > +	if (!fh) {
> > > > +		ret = -errno;
> > > > +		LOG(HALConfig, Error) << "Failed to open file " << filePath
> > > > +				      << ": " << strerror(-ret);
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	yaml_parser_set_input_file(&parser_, fh);
> > > > +
> > > > +	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
> > > > +
> > > > +	ret = parseConfigFile();
> > > > +	fclose(fh);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	LOG(HALConfig, Info) << "Device model: " << model_;
> > > > +	LOG(HALConfig, Info) << "Device maker: " << maker_;
> > > > +	for (const auto &c : cameras_) {
> > > > +		const CameraBlock camera = c.second;
> > > > +		LOG(HALConfig, Info) << camera.toString();
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
> > > > +{
> > > > +	static const std::string empty("");
> > > > +	const auto &it = cameras_.find(camera);
> > > > +	if (it == cameras_.end()) {
> > > > +		LOG(HALConfig, Error)
> > > > +			<< "Camera '" << camera
> > > > +			<< "' not described in the HAL configuration file";
> > > > +		return empty;
> > > > +	}
> > > > +
> > > > +	const CameraBlock &cam = it->second;
> > > > +	return cam.location;
> > > > +}
> > > > +
> > > > +unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
> > > > +{
> > > > +	static const std::string empty("");
> > >
> > > empty not used.
> > >
> > 
> > Ouch, leftover
> > 
> > > > +	const auto &it = cameras_.find(camera);
> > > > +	if (it == cameras_.end()) {
> > > > +		LOG(HALConfig, Error)
> > > > +			<< "Camera '" << camera
> > > > +			<< "' not described in the HAL configuration file";
> > > > +		return 0;
> > > > +	}
> > > > +
> > > > +	const CameraBlock &cam = it->second;
> > > > +	return cam.rotation;
> > > > +}
> > > > +
> > > > +const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
> > > > +{
> > > > +	static const std::string empty("");
> > > > +	const auto &it = cameras_.find(camera);
> > > > +	if (it == cameras_.end()) {
> > > > +		LOG(HALConfig, Error)
> > > > +			<< "Camera '" << camera
> > > > +			<< "' not described in the HAL configuration file";
> > > > +		return empty;
> > > > +	}
> > > > +
> > > > +	const CameraBlock &cam = it->second;
> > > > +	return cam.model;
> > > > +}
> > > > +
> > > > +std::string CameraHalConfig::parseValue(yaml_token_t &token)
> > > > +{
> > > > +	/* Make sure the token type is a value and get its content. */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +
> > > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > > +	std::string value(scalar, token.data.scalar.length);
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	return value;
> > > > +}
> > > > +
> > > > +std::string CameraHalConfig::parseKey(yaml_token_t &token)
> > > > +{
> > > > +	/* Make sure the token type is a key and get its value. */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_KEY_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_SCALAR_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return "";
> > > > +	}
> > > > +
> > > > +	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
> > > > +	std::string key(scalar, token.data.scalar.length);
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	return key;
> > > > +}
> > > > +
> > > > +int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
> > > > +{
> > > > +	/* The 'camera' block is a VALUE token type. */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_VALUE_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	/*
> > > > +	 * Parse the content of the camera block until we have an unbalanced
> > > > +	 * BLOCK_END_TOKEN which closes the camera block.
> > > > +	 *
> > > > +	 * Add a safety counter to make sure we don't loop indefinitely in case
> > > > +	 * the configuration file is malformed.
> > > > +	 */
> > > > +	unsigned int sentinel = 100;
> > > > +	CameraBlock cameraBlock{};
> > > > +	int blockCounter = 0;
> > > > +	do {
> > > > +		yaml_parser_scan(&parser_, &token);
> > > > +		switch (token.type) {
> > > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > > +			yaml_token_delete(&token);
> > > > +			blockCounter++;
> > > > +			break;
> > > > +		case YAML_BLOCK_END_TOKEN:
> > > > +			yaml_token_delete(&token);
> > > > +			blockCounter--;
> > > > +			break;
> > > > +		case YAML_BLOCK_MAPPING_START_TOKEN: {
> > > > +			yaml_token_delete(&token);
> > > > +
> > > > +			std::string key = parseKey(token);
> > > > +			std::string value = parseValue(token);
> > > > +			if (key.empty() || value.empty()) {
> > > > +				LOG(HALConfig, Error)
> > > > +					<< "Configuration file is not valid";
> > > > +				return -EINVAL;
> > > > +			}
> > > > +
> > > > +			/* Validate that the parsed key is valid. */
> > > > +			if (key == "location") {
> > > > +				if (value != "front" && value != "back" &&
> > > > +				    value != "external") {
> > > > +					LOG(HALConfig, Error)
> > > > +						<< "Unknown location: " << value;
> > > > +					return -EINVAL;
> > > > +				}
> > > > +				cameraBlock.location = value;
> > > > +			} else if (key == "rotation") {
> > > > +				cameraBlock.rotation = strtoul(value.c_str(),
> > > > +							       NULL, 10);
> > > > +				if (cameraBlock.rotation < 0) {
> > > > +					LOG(HALConfig, Error)
> > > > +						<< "Unknown rotation: "
> > > > +						<< cameraBlock.rotation;
> > > > +					return -EINVAL;
> > > > +				}
> > > > +			} else if (key == "name") {
> > > > +				cameraBlock.name = value;
> > > > +			} else if (key == "model") {
> > > > +				cameraBlock.model = value;
> > > > +			} else {
> > > > +				LOG(HALConfig, Error)
> > > > +					<< "Configuration file is not valid "
> > > > +					<< "unknown key: " << key;
> > > > +				return -EINVAL;
> > > > +			}
> > > > +			break;
> > > > +		}
> > > > +		default:
> > > > +			yaml_token_delete(&token);
> > > > +			break;
> > > > +		}
> > > > +		--sentinel;
> > > > +	} while (blockCounter >= 0 && sentinel);
> > > > +	if (!sentinel) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid ";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	cameras_[cameraBlock.name] = cameraBlock;
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
> > > > +{
> > > > +	int ret;
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	std::string key = parseKey(token);
> > > > +	if (key.empty()) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	if (key == "camera") {
> > > > +		yaml_token_delete(&token);
> > > > +		ret = parseCameraBlock(token);
> > > > +		if (ret)
> > > > +			return ret;
> > > > +	} else if (key == "manufacturer") {
> > > > +		yaml_token_delete(&token);
> > > > +		maker_ = parseValue(token);
> > > > +		if (maker_.empty()) {
> > > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +			return -EINVAL;
> > > > +		}
> > > > +	} else if (key == "model") {
> > > > +		yaml_token_delete(&token);
> > > > +		model_ = parseValue(token);
> > > > +		if (model_.empty()) {
> > > > +			LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +			return -EINVAL;
> > > > +		}
> > > > +	} else {
> > > > +		yaml_token_delete(&token);
> > > > +		LOG(HALConfig, Error) << "Unknown key " << key;
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +int CameraHalConfig::parseConfigFile()
> > > > +{
> > > > +	yaml_token_t token;
> > > > +	int ret;
> > > > +
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_STREAM_START_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	/*
> > > > +	 * Start parsing the content of the configuration file which
> > > > +	 * starts as with sequence block.
> > > > +	 */
> > > > +	yaml_parser_scan(&parser_, &token);
> > > > +	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
> > > > +		LOG(HALConfig, Error) << "Configuration file is not valid";
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	/* Parse the single entry blocks. */
> > > > +	do {
> > > > +		yaml_parser_scan(&parser_, &token);
> > > > +		switch (token.type) {
> > > > +		case YAML_BLOCK_ENTRY_TOKEN:
> > > > +			yaml_token_delete(&token);
> > > > +			ret = parseEntryBlock(token);
> > > > +			break;
> > > > +		case YAML_STREAM_END_TOKEN:
> > > > +			/* Resources are released after the loop exit. */
> > > > +			break;
> > > > +		default:
> > > > +			/* Release resources before re-parsing. */
> > > > +			yaml_token_delete(&token);
> > > > +			break;
> > > > +		}
> > > > +	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
> > > > +	yaml_token_delete(&token);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
> > > > new file mode 100644
> > > > index 000000000000..69d42658e90a
> > > > --- /dev/null
> > > > +++ b/src/android/camera_hal_config.h
> > > > @@ -0,0 +1,54 @@
> > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > > +/*
> > > > + * Copyright (C) 2021, Google Inc.
> > > > + *
> > > > + * camera_hal_config.h - Camera HAL configuration file manager
> > > > + */
> > > > +#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
> > > > +#define __ANDROID_CAMERA_HAL_CONFIG_H__
> > > > +
> > > > +#include <map>
> > > > +#include <string>
> > > > +#include <yaml.h>
> > > > +
> > > > +class CameraHalConfig
> > > > +{
> > > > +public:
> > > > +	CameraHalConfig();
> > > > +	~CameraHalConfig();
> > > > +
> > > > +	int open();
> > > > +
> > > > +	const std::string &deviceModel() const { return model_; }
> > > > +	const std::string &deviceMaker() const { return maker_; }
> > > > +
> > > > +	const std::string &cameraLocation(const std::string &camera) const;
> > > > +	unsigned int cameraRotation(const std::string &camera) const;
> > > > +	const std::string &cameraModel(const std::string &camera) const;
> > > > +
> > > > +private:
> > > > +	yaml_parser_t parser_;
> > > > +
> > > > +	std::string model_;
> > > > +	std::string maker_;
> > > > +	class CameraBlock
> > > > +	{
> > > > +	public:
> > > > +		std::string name;
> > > > +		std::string location;
> > > > +		std::string model;
> > > > +		unsigned int rotation;
> > > > +
> > > > +		const std::string toString() const;
> > > > +	};
> > > > +	std::map<std::string, CameraBlock> cameras_;
> > > > +
> > > > +	const std::string findFilePath(const std::string &filename);
> > > > +	std::string parseValue(yaml_token_t &token);
> > > > +	std::string parseKey(yaml_token_t &token);
> > > > +	int parseCameraBlock(yaml_token_t &token);
> > > > +	int parseEntryBlock(yaml_token_t &token);
> > > > +	int parseConfigFile();
> > > > +};
> > > > +
> > > > +#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
> > > > diff --git a/src/android/meson.build b/src/android/meson.build
> > > > index 19f94a4073f1..34e5c494a492 100644
> > > > --- a/src/android/meson.build
> > > > +++ b/src/android/meson.build
> > > > @@ -44,6 +44,7 @@ android_hal_sources = files([
> > > >      'camera3_hal.cpp',
> > > >      'camera_hal_manager.cpp',
> > > >      'camera_device.cpp',
> > > > +    'camera_hal_config.cpp',
> > > >      'camera_metadata.cpp',
> > > >      'camera_ops.cpp',
> > > >      'camera_stream.cpp',

Patch
diff mbox series

diff --git a/src/android/camera_hal_config.cpp b/src/android/camera_hal_config.cpp
new file mode 100644
index 000000000000..7e33dfb750ff
--- /dev/null
+++ b/src/android/camera_hal_config.cpp
@@ -0,0 +1,385 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * camera_hal_config.cpp - Camera HAL configuration file manager
+ */
+#include "camera_hal_config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libcamera/internal/file.h"
+#include "libcamera/internal/log.h"
+
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(HALConfig)
+
+const std::string CameraHalConfig::CameraBlock::toString() const
+{
+	std::stringstream ss;
+
+	ss << "\'" << name << "\'"
+	   << " model: " << model
+	   << "(" << location << ")[" << rotation << "]";
+
+	return ss.str();
+}
+
+CameraHalConfig::CameraHalConfig()
+{
+	if (!yaml_parser_initialize(&parser_))
+		LOG(HALConfig, Error) << "Failed to initialize yaml parser";
+}
+
+CameraHalConfig::~CameraHalConfig()
+{
+	yaml_parser_delete(&parser_);
+}
+
+/*
+ * Configuration files can be stored in system paths, which are identified
+ * through the build configuration.
+ *
+ * However, when running uninstalled - the source location takes precedence.
+ */
+const std::string CameraHalConfig::findFilePath(const std::string &filename)
+{
+	static std::array<std::string, 2> searchPaths = {
+		LIBCAMERA_SYSCONF_DIR,
+		LIBCAMERA_DATA_DIR,
+	};
+
+	if (File::exists(filename))
+		return filename;
+
+	std::string root = utils::libcameraSourcePath();
+	if (!root.empty()) {
+		std::string configurationPath = root + "data/" + filename;
+
+		if (File::exists(configurationPath))
+			return configurationPath;
+	}
+
+	for (std::string &path : searchPaths) {
+		std::string configurationPath = path + "/" + filename;
+		if (File::exists(configurationPath))
+			return configurationPath;
+	}
+
+	return "";
+}
+
+/*
+ * \brief Open the HAL configuration file and validate its content
+ * \return 0 on success, a negative error code otherwise
+ */
+int CameraHalConfig::open()
+{
+	int ret;
+
+	const std::string configPath = "camera_hal.yaml";
+	const std::string filePath = findFilePath(configPath);
+	if (filePath.empty()) {
+		LOG(HALConfig, Warning)
+			<< "File: \"" << configPath << "\" not found";
+		return -ENOENT;
+	}
+
+	FILE *fh = fopen(filePath.c_str(), "r");
+	if (!fh) {
+		ret = -errno;
+		LOG(HALConfig, Error) << "Failed to open file " << filePath
+				      << ": " << strerror(-ret);
+		return ret;
+	}
+
+	yaml_parser_set_input_file(&parser_, fh);
+
+	LOG(HALConfig, Debug) << "Reading configuration from " << filePath;
+
+	ret = parseConfigFile();
+	fclose(fh);
+	if (ret)
+		return ret;
+
+	LOG(HALConfig, Info) << "Device model: " << model_;
+	LOG(HALConfig, Info) << "Device maker: " << maker_;
+	for (const auto &c : cameras_) {
+		const CameraBlock camera = c.second;
+		LOG(HALConfig, Info) << camera.toString();
+	}
+
+	return 0;
+}
+
+const std::string &CameraHalConfig::cameraLocation(const std::string &camera) const
+{
+	static const std::string empty("");
+	const auto &it = cameras_.find(camera);
+	if (it == cameras_.end()) {
+		LOG(HALConfig, Error)
+			<< "Camera '" << camera
+			<< "' not described in the HAL configuration file";
+		return empty;
+	}
+
+	const CameraBlock &cam = it->second;
+	return cam.location;
+}
+
+unsigned int CameraHalConfig::cameraRotation(const std::string &camera) const
+{
+	static const std::string empty("");
+	const auto &it = cameras_.find(camera);
+	if (it == cameras_.end()) {
+		LOG(HALConfig, Error)
+			<< "Camera '" << camera
+			<< "' not described in the HAL configuration file";
+		return 0;
+	}
+
+	const CameraBlock &cam = it->second;
+	return cam.rotation;
+}
+
+const std::string &CameraHalConfig::cameraModel(const std::string &camera) const
+{
+	static const std::string empty("");
+	const auto &it = cameras_.find(camera);
+	if (it == cameras_.end()) {
+		LOG(HALConfig, Error)
+			<< "Camera '" << camera
+			<< "' not described in the HAL configuration file";
+		return empty;
+	}
+
+	const CameraBlock &cam = it->second;
+	return cam.model;
+}
+
+std::string CameraHalConfig::parseValue(yaml_token_t &token)
+{
+	/* Make sure the token type is a value and get its content. */
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_VALUE_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return "";
+	}
+	yaml_token_delete(&token);
+
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_SCALAR_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return "";
+	}
+
+	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
+	std::string value(scalar, token.data.scalar.length);
+	yaml_token_delete(&token);
+
+	return value;
+}
+
+std::string CameraHalConfig::parseKey(yaml_token_t &token)
+{
+	/* Make sure the token type is a key and get its value. */
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_KEY_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return "";
+	}
+	yaml_token_delete(&token);
+
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_SCALAR_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return "";
+	}
+
+	char *scalar = reinterpret_cast<char *>(token.data.scalar.value);
+	std::string key(scalar, token.data.scalar.length);
+	yaml_token_delete(&token);
+
+	return key;
+}
+
+int CameraHalConfig::parseCameraBlock(yaml_token_t &token)
+{
+	/* The 'camera' block is a VALUE token type. */
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_VALUE_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return -EINVAL;
+	}
+	yaml_token_delete(&token);
+
+	/*
+	 * Parse the content of the camera block until we have an unbalanced
+	 * BLOCK_END_TOKEN which closes the camera block.
+	 *
+	 * Add a safety counter to make sure we don't loop indefinitely in case
+	 * the configuration file is malformed.
+	 */
+	unsigned int sentinel = 100;
+	CameraBlock cameraBlock{};
+	int blockCounter = 0;
+	do {
+		yaml_parser_scan(&parser_, &token);
+		switch (token.type) {
+		case YAML_BLOCK_ENTRY_TOKEN:
+			yaml_token_delete(&token);
+			blockCounter++;
+			break;
+		case YAML_BLOCK_END_TOKEN:
+			yaml_token_delete(&token);
+			blockCounter--;
+			break;
+		case YAML_BLOCK_MAPPING_START_TOKEN: {
+			yaml_token_delete(&token);
+
+			std::string key = parseKey(token);
+			std::string value = parseValue(token);
+			if (key.empty() || value.empty()) {
+				LOG(HALConfig, Error)
+					<< "Configuration file is not valid";
+				return -EINVAL;
+			}
+
+			/* Validate that the parsed key is valid. */
+			if (key == "location") {
+				if (value != "front" && value != "back" &&
+				    value != "external") {
+					LOG(HALConfig, Error)
+						<< "Unknown location: " << value;
+					return -EINVAL;
+				}
+				cameraBlock.location = value;
+			} else if (key == "rotation") {
+				cameraBlock.rotation = strtoul(value.c_str(),
+							       NULL, 10);
+				if (cameraBlock.rotation < 0) {
+					LOG(HALConfig, Error)
+						<< "Unknown rotation: "
+						<< cameraBlock.rotation;
+					return -EINVAL;
+				}
+			} else if (key == "name") {
+				cameraBlock.name = value;
+			} else if (key == "model") {
+				cameraBlock.model = value;
+			} else {
+				LOG(HALConfig, Error)
+					<< "Configuration file is not valid "
+					<< "unknown key: " << key;
+				return -EINVAL;
+			}
+			break;
+		}
+		default:
+			yaml_token_delete(&token);
+			break;
+		}
+		--sentinel;
+	} while (blockCounter >= 0 && sentinel);
+	if (!sentinel) {
+		LOG(HALConfig, Error) << "Configuration file is not valid ";
+		return -EINVAL;
+	}
+
+	cameras_[cameraBlock.name] = cameraBlock;
+
+	return 0;
+}
+
+int CameraHalConfig::parseEntryBlock(yaml_token_t &token)
+{
+	int ret;
+
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_BLOCK_MAPPING_START_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return -EINVAL;
+	}
+	yaml_token_delete(&token);
+
+	std::string key = parseKey(token);
+	if (key.empty()) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return -EINVAL;
+	}
+
+	if (key == "camera") {
+		yaml_token_delete(&token);
+		ret = parseCameraBlock(token);
+		if (ret)
+			return ret;
+	} else if (key == "manufacturer") {
+		yaml_token_delete(&token);
+		maker_ = parseValue(token);
+		if (maker_.empty()) {
+			LOG(HALConfig, Error) << "Configuration file is not valid";
+			return -EINVAL;
+		}
+	} else if (key == "model") {
+		yaml_token_delete(&token);
+		model_ = parseValue(token);
+		if (model_.empty()) {
+			LOG(HALConfig, Error) << "Configuration file is not valid";
+			return -EINVAL;
+		}
+	} else {
+		yaml_token_delete(&token);
+		LOG(HALConfig, Error) << "Unknown key " << key;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int CameraHalConfig::parseConfigFile()
+{
+	yaml_token_t token;
+	int ret;
+
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_STREAM_START_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return EINVAL;
+	}
+	yaml_token_delete(&token);
+
+	/*
+	 * Start parsing the content of the configuration file which
+	 * starts as with sequence block.
+	 */
+	yaml_parser_scan(&parser_, &token);
+	if (token.type != YAML_BLOCK_SEQUENCE_START_TOKEN) {
+		LOG(HALConfig, Error) << "Configuration file is not valid";
+		return -EINVAL;
+	}
+	yaml_token_delete(&token);
+
+	/* Parse the single entry blocks. */
+	do {
+		yaml_parser_scan(&parser_, &token);
+		switch (token.type) {
+		case YAML_BLOCK_ENTRY_TOKEN:
+			yaml_token_delete(&token);
+			ret = parseEntryBlock(token);
+			break;
+		case YAML_STREAM_END_TOKEN:
+			/* Resources are released after the loop exit. */
+			break;
+		default:
+			/* Release resources before re-parsing. */
+			yaml_token_delete(&token);
+			break;
+		}
+	} while (token.type != YAML_STREAM_END_TOKEN && ret >= 0);
+	yaml_token_delete(&token);
+
+	return ret;
+}
diff --git a/src/android/camera_hal_config.h b/src/android/camera_hal_config.h
new file mode 100644
index 000000000000..69d42658e90a
--- /dev/null
+++ b/src/android/camera_hal_config.h
@@ -0,0 +1,54 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021, Google Inc.
+ *
+ * camera_hal_config.h - Camera HAL configuration file manager
+ */
+#ifndef __ANDROID_CAMERA_HAL_CONFIG_H__
+#define __ANDROID_CAMERA_HAL_CONFIG_H__
+
+#include <map>
+#include <string>
+#include <yaml.h>
+
+class CameraHalConfig
+{
+public:
+	CameraHalConfig();
+	~CameraHalConfig();
+
+	int open();
+
+	const std::string &deviceModel() const { return model_; }
+	const std::string &deviceMaker() const { return maker_; }
+
+	const std::string &cameraLocation(const std::string &camera) const;
+	unsigned int cameraRotation(const std::string &camera) const;
+	const std::string &cameraModel(const std::string &camera) const;
+
+private:
+	yaml_parser_t parser_;
+
+	std::string model_;
+	std::string maker_;
+	class CameraBlock
+	{
+	public:
+		std::string name;
+		std::string location;
+		std::string model;
+		unsigned int rotation;
+
+		const std::string toString() const;
+	};
+	std::map<std::string, CameraBlock> cameras_;
+
+	const std::string findFilePath(const std::string &filename);
+	std::string parseValue(yaml_token_t &token);
+	std::string parseKey(yaml_token_t &token);
+	int parseCameraBlock(yaml_token_t &token);
+	int parseEntryBlock(yaml_token_t &token);
+	int parseConfigFile();
+};
+
+#endif /* __ANDROID_CAMERA_HAL_CONFIG_H__ */
diff --git a/src/android/meson.build b/src/android/meson.build
index 19f94a4073f1..34e5c494a492 100644
--- a/src/android/meson.build
+++ b/src/android/meson.build
@@ -44,6 +44,7 @@  android_hal_sources = files([
     'camera3_hal.cpp',
     'camera_hal_manager.cpp',
     'camera_device.cpp',
+    'camera_hal_config.cpp',
     'camera_metadata.cpp',
     'camera_ops.cpp',
     'camera_stream.cpp',