@@ -19,6 +19,7 @@ libipa_headers = files([
'pwl.h',
'quantized.h',
'v4l2_params.h',
+ 'v4l2_stats.h',
])
libipa_sources = files([
@@ -40,6 +41,7 @@ libipa_sources = files([
'pwl.cpp',
'quantized.cpp',
'v4l2_params.cpp',
+ 'v4l2_stats.cpp',
])
libipa_includes = include_directories('..')
new file mode 100644
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Ideas On Board
+ *
+ * V4L2 ISP Statistics
+ */
+
+#include "v4l2_stats.h"
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+namespace ipa {
+
+LOG_DEFINE_CATEGORY(V4L2Stats)
+
+/**
+ * \file v4l2_stats.cpp
+ * \brief Helper class to handle an ISP statistics buffer compatible with
+ * the generic V4L2 ISP format
+ *
+ * The Linux kernel defines a generic buffer format for ISP statistics.
+ * The format describes a serialisation method that allows userspace to
+ * access statistics data from a binary buffer.
+ *
+ * The V4L2Stats class implements support for the V4L2 ISP statistics buffer
+ * format and allows users to retrieve an ISP statistics block.
+ *
+ * IPA implementations using these helpers should define an enumeration of ISP
+ * blocks supported by the IPA module and use a set of common abstractions to
+ * help their derived implementation of V4L2Stats translate the enumerated ISP
+ * block identifiers to the actual type of the statistics data as defined by
+ * the kernel interface.
+ */
+
+/**
+ * \class V4L2StatsBase
+ * \brief Base class for V4L2Stats
+ *
+ * The V4L2StatsBase is an integral part of V4L2Stats. It serves as a
+ * container for all code that does not depend on the V4L2Stats template
+ * arguments, to avoid duplicate copies of inline code.
+ */
+
+/**
+ * \brief Construct an instance of V4L2StatsBase
+ * \param[in] data Reference to the v4l2-buffer memory mapped area
+ * \param[in] version The ISP parameters version the implementation supports
+ *
+ * Parse the statistics buffer and construct a cache that maps the block type to
+ * the memory location of the statistics block in the buffer.
+ *
+ * After construction users of this class shall check the validity of the
+ * constructed instance using operator bool().
+ */
+V4L2StatsBase::V4L2StatsBase(Span<uint8_t> data, unsigned int version)
+ : data_(data), valid_(false)
+{
+ const struct v4l2_isp_buffer *stats =
+ reinterpret_cast<const struct v4l2_isp_buffer *>(data_.data());
+
+ if (data_.size() - sizeof(*stats) < stats->data_size) {
+ LOG(V4L2Stats, Error)
+ << "Stats buffer size mismatch: " << stats->data_size;
+ return;
+ }
+
+ if (version != stats->version) {
+ LOG(V4L2Stats, Error)
+ << "Unsupported v4l2-isp version: " << stats->version;
+ return;
+ }
+
+ /* Construct the cache for easier lookup. */
+ size_t left = stats->data_size;
+ const __u8 *d = stats->data;
+
+ while (left > 0) {
+ const struct v4l2_isp_block_header *header =
+ reinterpret_cast<const struct v4l2_isp_block_header *>(d);
+
+ if (header->size > stats->data + stats->data_size - d) {
+ LOG(V4L2Stats, Error)
+ << "Block type " << header->type
+ << " size is not valid";
+ return;
+ }
+
+ cache_[header->type] = Span<const uint8_t>(d, header->size);
+ d += header->size;
+ left -= header->size;
+ }
+
+ valid_ = true;
+}
+
+/**
+ * \brief Retrieve an ISP statistics block a returns a reference to it
+ * \param[in] blockType The kernel-defined ISP block identifier, used to
+ * identify the block header
+ * \param[in] blockSize The ISP statistics block size, for validation
+ *
+ * Retrieve a span to the statistics block memory location by accessing the
+ * cache built at class construction time.
+ *
+ * \return The memory location of the ISP statistics block, or an empty Span
+ * if \a blockType is not supported
+ */
+Span<const uint8_t> V4L2StatsBase::block(unsigned int blockType, size_t blockSize) const
+{
+ const auto it = cache_.find(blockType);
+ if (it == cache_.end()) {
+ LOG(V4L2Stats, Error) << "Unsupported stats block type: "
+ << blockType;
+ return {};
+ }
+
+ const struct v4l2_isp_block_header *header =
+ reinterpret_cast<const struct v4l2_isp_block_header *>(it->second.data());
+ if (header->size != blockSize) {
+ LOG(V4L2Stats, Error)
+ << "Block type " << blockType
+ << " size mistmatch: expected "
+ << blockSize << " got:"
+ << header->size;
+ return {};
+ }
+
+ return it->second;
+}
+
+/**
+ * \fn V4L2StatsBase::operator bool()
+ * \brief Retrieve if a V4L2StatsBase has been successfully constructed
+ * \return True if the instance has been constructed successfully, false
+ * otherwise
+ */
+
+/**
+ * \var V4L2StatsBase::cache_
+ * \brief Map the statistics block types to the memory area where stats are
+ * located
+ */
+
+/**
+ * \var V4L2StatsBase::data_
+ * \brief The ISP statistics buffer memory
+ */
+
+/**
+ * \var V4L2StatsBase::valid_
+ * \brief Flag to signal valid construction of a V4L2StatsBase instance
+ */
+
+/**
+ * \class V4L2Stats
+ * \brief Helper class that represent an ISP statistics buffer
+ *
+ * This class represents an ISP statistics buffer. It is constructed with a
+ * reference to the memory mapped buffer that has been dequeued from the ISP
+ * driver.
+ *
+ * This class is templated with the type of the enumeration of ISP blocks that
+ * each IPA module is expected to support. IPA modules are expected to derive
+ * this class by providing a 'stats_traits' type that helps the class associate
+ * a block type with the actual memory area that represents the ISP statistics
+ * block.
+ *
+ * \code{.cpp}
+ *
+ * // Define the supported ISP statistics blocks
+ * enum class myISPStats {
+ * Agc,
+ * Awb,
+ * ...
+ * };
+ *
+ * // Maps the C++ enum type to the kernel enum type and concrete parameter type
+ * template<myISPStats B>
+ * struct block_type {
+ * };
+ *
+ * template<>
+ * struct block_type<myISPStats::Agc> {
+ * using type = struct my_isp_kernel_stats_type_agc;
+ * static constexpr kernel_enum_type blockType = MY_ISP_STATS_TYPE_AGC;
+ * };
+ *
+ * template<>
+ * struct block_type<myISPStats::Awb> {
+ * using type = struct my_isp_kernel_stats_type_awb;
+ * static constexpr kernel_enum_type blockType = MY_ISP_STATS_TYPE_AWB;
+ * };
+ *
+ *
+ * // Convenience type to associate a block id to the 'block_type' overload
+ * struct stats_traits {
+ * using id_type = myISPStats;
+ * template<id_type Id> using id_to_details = block_type<Id>;
+ * };
+ *
+ * ...
+ *
+ * // Derive the V4L2Stats class by providing stats_traits; return an
+ * // std::optional to make it easy to check if the class has been constructed
+ * // correctly
+ * class MyISPStats : public V4L2Stats<stats_traits>
+ * {
+ * public:
+ * static std::optional<MyISPStats> from(Span<uint8_t> data)
+ * {
+ * MyISPStats s(data, V4L2_ISP_VERSION_V..);
+ *
+ * return s ? std::make_optional(s) : std::nullopt;
+ * }
+ *
+ * private:
+ * MyISPStats::MyISPStats(Span<uint8_t> data, unsigned int version)
+ * : V4L2Stats(data, version)
+ * {
+ * }
+ * };
+ *
+ * \endcode
+ *
+ * Users of this class can then easily access an ISP statistics block using the
+ * block() function.
+ *
+ * \code{.cpp}
+ *
+ * MyISPStats stats(data);
+ *
+ * auto awb = stats.block<myISPStats::AWB>();
+ * auto mean_r = awb->mean_r;
+ * auto mean_g = awb->mean_g;
+ * auto mean_b = awb->mean_b;
+ * \endcode
+ */
+
+/**
+ * \fn V4L2Stats::V4L2Stats()
+ * \brief Construct an instance of V4L2Stats
+ * \param[in] data Reference to the v4l2-buffer memory mapped area
+ * \param[in] version The expected V4L2 ISP serialization format version
+ */
+
+/**
+ * \fn V4L2Stats::block() const
+ * \brief Retrieve a pointer to an ISP statistics block
+ * \return A pointer to the ISP statistics block
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Ideas On Board
+ *
+ * V4L2 ISP Statistics
+ */
+
+#pragma once
+
+#include <map>
+#include <stdint.h>
+
+#include <linux/media/v4l2-isp.h>
+
+#include <libcamera/base/span.h>
+
+namespace libcamera {
+
+namespace ipa {
+
+class V4L2StatsBase
+{
+protected:
+ V4L2StatsBase(Span<uint8_t> data, unsigned int version);
+
+ Span<const uint8_t> block(unsigned int blockType, size_t blockSize) const;
+ constexpr explicit operator bool()
+ {
+ return valid_;
+ }
+
+ std::map<uint16_t, Span<const uint8_t>> cache_;
+ Span<uint8_t> data_;
+ bool valid_;
+};
+
+template<typename Traits>
+class V4L2Stats : public V4L2StatsBase
+{
+public:
+ static_assert(std::is_same_v<std::underlying_type_t<typename Traits::id_type>, uint16_t>);
+
+ template<typename Traits::id_type Id>
+ const typename Traits::template id_to_details<Id>::type *
+ block() const
+ {
+ using Details = typename Traits::template id_to_details<Id>;
+
+ using Type = typename Details::type;
+ constexpr auto kernelId = Details::blockType;
+
+ auto data = V4L2StatsBase::block(kernelId, sizeof(Type));
+
+ return data.size() > 0 ?
+ reinterpret_cast<const Type *>(data.data()) : nullptr;
+ }
+
+protected:
+ V4L2Stats(Span<uint8_t> data, unsigned int version)
+ : V4L2StatsBase(data, version)
+ {
+ }
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */