[RFC,v2,09/22] libcamera: ipa_data_serializer: Support `MetadataListPlan`
diff mbox series

Message ID 20250721104622.1550908-10-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • libcamera: Add `MetadataList`
Related show

Commit Message

Barnabás Pőcze July 21, 2025, 10:46 a.m. UTC
Define the type in `core.mojom` with external (de)serialization, and
add the necessary `IPADataSerializer` template specialization.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 include/libcamera/ipa/core.mojom              |  1 +
 src/libcamera/ipa_data_serializer.cpp         | 91 +++++++++++++++++++
 .../core_ipa_interface.h.tmpl                 |  1 +
 3 files changed, 93 insertions(+)

Comments

Paul Elder Sept. 18, 2025, 8:38 a.m. UTC | #1
Hi Barnabás,

Thanks for the patch.

Quoting Barnabás Pőcze (2025-07-21 19:46:09)
> Define the type in `core.mojom` with external (de)serialization, and
> add the necessary `IPADataSerializer` template specialization.
> 
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>  include/libcamera/ipa/core.mojom              |  1 +
>  src/libcamera/ipa_data_serializer.cpp         | 91 +++++++++++++++++++
>  .../core_ipa_interface.h.tmpl                 |  1 +
>  3 files changed, 93 insertions(+)
> 
> diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom
> index bce797245..754e4065c 100644
> --- a/include/libcamera/ipa/core.mojom
> +++ b/include/libcamera/ipa/core.mojom
> @@ -83,6 +83,7 @@ module libcamera;
>  [skipSerdes, skipHeader] struct ControlInfoMap {};
>  [skipSerdes, skipHeader] struct ControlList {};
>  [skipSerdes, skipHeader] struct SharedFD {};
> +[skipSerdes, skipHeader] struct MetadataListPlan {};
>  
>  [skipHeader] struct Point {
>         int32 x;
> diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
> index 0537f785b..26090f658 100644
> --- a/src/libcamera/ipa_data_serializer.cpp
> +++ b/src/libcamera/ipa_data_serializer.cpp
> @@ -11,6 +11,8 @@
>  
>  #include <libcamera/base/log.h>
>  
> +#include <libcamera/metadata_list_plan.h>
> +
>  #include "libcamera/internal/byte_stream_buffer.h"
>  
>  /**
> @@ -620,6 +622,95 @@ IPADataSerializer<FrameBuffer::Plane>::deserialize(const std::vector<uint8_t> &d
>         return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
>  }
>  
> +template<>
> +std::tuple<std::vector<uint8_t>, std::vector<SharedFD>>
> +IPADataSerializer<MetadataListPlan>::serialize(const MetadataListPlan &data,
> +                                              [[maybe_unused]] ControlSerializer *cs)
> +{
> +       std::vector<uint8_t> dataVec;
> +
> +       appendPOD<uint32_t>(dataVec, data.size());
> +
> +       for (const auto &[tag, e] : data) {
> +               appendPOD<uint32_t>(dataVec, tag);
> +               appendPOD<uint32_t>(dataVec, e.size);
> +               appendPOD<uint32_t>(dataVec, e.alignment);
> +               appendPOD<uint32_t>(dataVec, e.numElements);
> +               appendPOD<uint32_t>(dataVec, e.type);
> +               appendPOD<uint8_t>(dataVec, e.isArray);
> +       }
> +
> +       return { dataVec, {} };
> +}
> +
> +template<>
> +MetadataListPlan
> +IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> +                                                std::vector<uint8_t>::const_iterator dataEnd,
> +                                                [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsBegin,
> +                                                [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsEnd,
> +                                                [[maybe_unused]] ControlSerializer *cs)
> +{
> +       MetadataListPlan ret;
> +       std::size_t offset = 0;
> +
> +       auto n = readPOD<uint32_t>(dataBegin, 0, dataEnd);
> +       offset += sizeof(n);
> +
> +       while (n--) {
> +               auto tag = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> +               offset += sizeof(tag);
> +
> +               auto size = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> +               offset += sizeof(size);
> +
> +               auto alignment = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> +               offset += sizeof(alignment);
> +
> +               auto numElements = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> +               offset += sizeof(numElements);
> +
> +               auto type = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> +               offset += sizeof(type);
> +
> +               auto isArray = readPOD<uint8_t>(dataBegin, offset, dataEnd);
> +               offset += sizeof(isArray);
> +
> +               [[maybe_unused]] bool ok = ret.set(tag,
> +                                                  size, alignment,
> +                                                  numElements, static_cast<ControlType>(type), isArray);
> +               ASSERT(ok);

The other deserializers return empty data instead of loudly crashing
everything. Should we make the other deserializers loudly crash everything as
well or is it better to return an empty object?


Thanks,

Paul

> +       }
> +
> +       return ret;
> +}
> +
> +template<>
> +MetadataListPlan
> +IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> +                                                std::vector<uint8_t>::const_iterator dataEnd,
> +                                                ControlSerializer *cs)
> +{
> +       return deserialize(dataBegin, dataEnd, {}, {}, cs);
> +}
> +
> +template<>
> +MetadataListPlan
> +IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
> +                                                ControlSerializer *cs)
> +{
> +       return deserialize(data.cbegin(), data.end(), cs);
> +}
> +
> +template<>
> +MetadataListPlan
> +IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
> +                                                const std::vector<SharedFD> &fds,
> +                                                ControlSerializer *cs)
> +{
> +       return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
> +}
> +
>  #endif /* __DOXYGEN__ */
>  
>  } /* namespace libcamera */
> diff --git a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> index 3942e5708..b3774cd64 100644
> --- a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> @@ -21,6 +21,7 @@
>  #include <libcamera/controls.h>
>  #include <libcamera/framebuffer.h>
>  #include <libcamera/geometry.h>
> +#include <libcamera/metadata_list_plan.h>
>  
>  #include <libcamera/ipa/ipa_interface.h>
>  
> -- 
> 2.50.1
>
Barnabás Pőcze Sept. 18, 2025, 8:51 a.m. UTC | #2
2025. 09. 18. 10:38 keltezéssel, Paul Elder írta:
> Hi Barnabás,
> 
> Thanks for the patch.
> 
> Quoting Barnabás Pőcze (2025-07-21 19:46:09)
>> Define the type in `core.mojom` with external (de)serialization, and
>> add the necessary `IPADataSerializer` template specialization.
>>
>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
>> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>> ---
>>   include/libcamera/ipa/core.mojom              |  1 +
>>   src/libcamera/ipa_data_serializer.cpp         | 91 +++++++++++++++++++
>>   .../core_ipa_interface.h.tmpl                 |  1 +
>>   3 files changed, 93 insertions(+)
>>
>> diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom
>> index bce797245..754e4065c 100644
>> --- a/include/libcamera/ipa/core.mojom
>> +++ b/include/libcamera/ipa/core.mojom
>> @@ -83,6 +83,7 @@ module libcamera;
>>   [skipSerdes, skipHeader] struct ControlInfoMap {};
>>   [skipSerdes, skipHeader] struct ControlList {};
>>   [skipSerdes, skipHeader] struct SharedFD {};
>> +[skipSerdes, skipHeader] struct MetadataListPlan {};
>>
>>   [skipHeader] struct Point {
>>          int32 x;
>> diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
>> index 0537f785b..26090f658 100644
>> --- a/src/libcamera/ipa_data_serializer.cpp
>> +++ b/src/libcamera/ipa_data_serializer.cpp
>> @@ -11,6 +11,8 @@
>>
>>   #include <libcamera/base/log.h>
>>
>> +#include <libcamera/metadata_list_plan.h>
>> +
>>   #include "libcamera/internal/byte_stream_buffer.h"
>>
>>   /**
>> @@ -620,6 +622,95 @@ IPADataSerializer<FrameBuffer::Plane>::deserialize(const std::vector<uint8_t> &d
>>          return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
>>   }
>>
>> +template<>
>> +std::tuple<std::vector<uint8_t>, std::vector<SharedFD>>
>> +IPADataSerializer<MetadataListPlan>::serialize(const MetadataListPlan &data,
>> +                                              [[maybe_unused]] ControlSerializer *cs)
>> +{
>> +       std::vector<uint8_t> dataVec;
>> +
>> +       appendPOD<uint32_t>(dataVec, data.size());
>> +
>> +       for (const auto &[tag, e] : data) {
>> +               appendPOD<uint32_t>(dataVec, tag);
>> +               appendPOD<uint32_t>(dataVec, e.size);
>> +               appendPOD<uint32_t>(dataVec, e.alignment);
>> +               appendPOD<uint32_t>(dataVec, e.numElements);
>> +               appendPOD<uint32_t>(dataVec, e.type);
>> +               appendPOD<uint8_t>(dataVec, e.isArray);
>> +       }
>> +
>> +       return { dataVec, {} };
>> +}
>> +
>> +template<>
>> +MetadataListPlan
>> +IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
>> +                                                std::vector<uint8_t>::const_iterator dataEnd,
>> +                                                [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsBegin,
>> +                                                [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsEnd,
>> +                                                [[maybe_unused]] ControlSerializer *cs)
>> +{
>> +       MetadataListPlan ret;
>> +       std::size_t offset = 0;
>> +
>> +       auto n = readPOD<uint32_t>(dataBegin, 0, dataEnd);
>> +       offset += sizeof(n);
>> +
>> +       while (n--) {
>> +               auto tag = readPOD<uint32_t>(dataBegin, offset, dataEnd);
>> +               offset += sizeof(tag);
>> +
>> +               auto size = readPOD<uint32_t>(dataBegin, offset, dataEnd);
>> +               offset += sizeof(size);
>> +
>> +               auto alignment = readPOD<uint32_t>(dataBegin, offset, dataEnd);
>> +               offset += sizeof(alignment);
>> +
>> +               auto numElements = readPOD<uint32_t>(dataBegin, offset, dataEnd);
>> +               offset += sizeof(numElements);
>> +
>> +               auto type = readPOD<uint32_t>(dataBegin, offset, dataEnd);
>> +               offset += sizeof(type);
>> +
>> +               auto isArray = readPOD<uint8_t>(dataBegin, offset, dataEnd);
>> +               offset += sizeof(isArray);
>> +
>> +               [[maybe_unused]] bool ok = ret.set(tag,
>> +                                                  size, alignment,
>> +                                                  numElements, static_cast<ControlType>(type), isArray);
>> +               ASSERT(ok);
> 
> The other deserializers return empty data instead of loudly crashing
> everything. Should we make the other deserializers loudly crash everything as
> well or is it better to return an empty object?

I think this could be made to return an empty object. I'm hoping that a new version of
https://patchwork.libcamera.org/patch/23373/ will be merged at some point, and then the
error handling can be moved out of the deserializers.


Regards,
Barnabás Pőcze

> 
> 
> Thanks,
> 
> Paul
> 
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +template<>
>> +MetadataListPlan
>> +IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
>> +                                                std::vector<uint8_t>::const_iterator dataEnd,
>> +                                                ControlSerializer *cs)
>> +{
>> +       return deserialize(dataBegin, dataEnd, {}, {}, cs);
>> +}
>> +
>> +template<>
>> +MetadataListPlan
>> +IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
>> +                                                ControlSerializer *cs)
>> +{
>> +       return deserialize(data.cbegin(), data.end(), cs);
>> +}
>> +
>> +template<>
>> +MetadataListPlan
>> +IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
>> +                                                const std::vector<SharedFD> &fds,
>> +                                                ControlSerializer *cs)
>> +{
>> +       return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
>> +}
>> +
>>   #endif /* __DOXYGEN__ */
>>
>>   } /* namespace libcamera */
>> diff --git a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
>> index 3942e5708..b3774cd64 100644
>> --- a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
>> +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
>> @@ -21,6 +21,7 @@
>>   #include <libcamera/controls.h>
>>   #include <libcamera/framebuffer.h>
>>   #include <libcamera/geometry.h>
>> +#include <libcamera/metadata_list_plan.h>
>>
>>   #include <libcamera/ipa/ipa_interface.h>
>>
>> --
>> 2.50.1
>>
Paul Elder Sept. 18, 2025, 1:18 p.m. UTC | #3
Quoting Barnabás Pőcze (2025-09-18 17:51:21)
> 2025. 09. 18. 10:38 keltezéssel, Paul Elder írta:
> > Hi Barnabás,
> > 
> > Thanks for the patch.
> > 
> > Quoting Barnabás Pőcze (2025-07-21 19:46:09)
> >> Define the type in `core.mojom` with external (de)serialization, and
> >> add the necessary `IPADataSerializer` template specialization.
> >>
> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> >> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> >> ---
> >>   include/libcamera/ipa/core.mojom              |  1 +
> >>   src/libcamera/ipa_data_serializer.cpp         | 91 +++++++++++++++++++
> >>   .../core_ipa_interface.h.tmpl                 |  1 +
> >>   3 files changed, 93 insertions(+)
> >>
> >> diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom
> >> index bce797245..754e4065c 100644
> >> --- a/include/libcamera/ipa/core.mojom
> >> +++ b/include/libcamera/ipa/core.mojom
> >> @@ -83,6 +83,7 @@ module libcamera;
> >>   [skipSerdes, skipHeader] struct ControlInfoMap {};
> >>   [skipSerdes, skipHeader] struct ControlList {};
> >>   [skipSerdes, skipHeader] struct SharedFD {};
> >> +[skipSerdes, skipHeader] struct MetadataListPlan {};
> >>
> >>   [skipHeader] struct Point {
> >>          int32 x;
> >> diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
> >> index 0537f785b..26090f658 100644
> >> --- a/src/libcamera/ipa_data_serializer.cpp
> >> +++ b/src/libcamera/ipa_data_serializer.cpp
> >> @@ -11,6 +11,8 @@
> >>
> >>   #include <libcamera/base/log.h>
> >>
> >> +#include <libcamera/metadata_list_plan.h>
> >> +
> >>   #include "libcamera/internal/byte_stream_buffer.h"
> >>
> >>   /**
> >> @@ -620,6 +622,95 @@ IPADataSerializer<FrameBuffer::Plane>::deserialize(const std::vector<uint8_t> &d
> >>          return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
> >>   }
> >>
> >> +template<>
> >> +std::tuple<std::vector<uint8_t>, std::vector<SharedFD>>
> >> +IPADataSerializer<MetadataListPlan>::serialize(const MetadataListPlan &data,
> >> +                                              [[maybe_unused]] ControlSerializer *cs)
> >> +{
> >> +       std::vector<uint8_t> dataVec;
> >> +
> >> +       appendPOD<uint32_t>(dataVec, data.size());
> >> +
> >> +       for (const auto &[tag, e] : data) {
> >> +               appendPOD<uint32_t>(dataVec, tag);
> >> +               appendPOD<uint32_t>(dataVec, e.size);
> >> +               appendPOD<uint32_t>(dataVec, e.alignment);
> >> +               appendPOD<uint32_t>(dataVec, e.numElements);
> >> +               appendPOD<uint32_t>(dataVec, e.type);
> >> +               appendPOD<uint8_t>(dataVec, e.isArray);
> >> +       }
> >> +
> >> +       return { dataVec, {} };
> >> +}
> >> +
> >> +template<>
> >> +MetadataListPlan
> >> +IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> >> +                                                std::vector<uint8_t>::const_iterator dataEnd,
> >> +                                                [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsBegin,
> >> +                                                [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsEnd,
> >> +                                                [[maybe_unused]] ControlSerializer *cs)
> >> +{
> >> +       MetadataListPlan ret;
> >> +       std::size_t offset = 0;
> >> +
> >> +       auto n = readPOD<uint32_t>(dataBegin, 0, dataEnd);
> >> +       offset += sizeof(n);
> >> +
> >> +       while (n--) {
> >> +               auto tag = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> >> +               offset += sizeof(tag);
> >> +
> >> +               auto size = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> >> +               offset += sizeof(size);
> >> +
> >> +               auto alignment = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> >> +               offset += sizeof(alignment);
> >> +
> >> +               auto numElements = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> >> +               offset += sizeof(numElements);
> >> +
> >> +               auto type = readPOD<uint32_t>(dataBegin, offset, dataEnd);
> >> +               offset += sizeof(type);
> >> +
> >> +               auto isArray = readPOD<uint8_t>(dataBegin, offset, dataEnd);
> >> +               offset += sizeof(isArray);
> >> +
> >> +               [[maybe_unused]] bool ok = ret.set(tag,
> >> +                                                  size, alignment,
> >> +                                                  numElements, static_cast<ControlType>(type), isArray);
> >> +               ASSERT(ok);
> > 
> > The other deserializers return empty data instead of loudly crashing
> > everything. Should we make the other deserializers loudly crash everything as
> > well or is it better to return an empty object?
> 
> I think this could be made to return an empty object. I'm hoping that a new version of
> https://patchwork.libcamera.org/patch/23373/ will be merged at some point, and then the
> error handling can be moved out of the deserializers.

Oh yeah it would be nice to get that in. Until that's in though I'd prefer this
to return an empty object instead of assert.

With that changed,

Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>

> 
> 
> Regards,
> Barnabás Pőcze
> 
> > 
> > 
> > Thanks,
> > 
> > Paul
> > 
> >> +       }
> >> +
> >> +       return ret;
> >> +}
> >> +
> >> +template<>
> >> +MetadataListPlan
> >> +IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
> >> +                                                std::vector<uint8_t>::const_iterator dataEnd,
> >> +                                                ControlSerializer *cs)
> >> +{
> >> +       return deserialize(dataBegin, dataEnd, {}, {}, cs);
> >> +}
> >> +
> >> +template<>
> >> +MetadataListPlan
> >> +IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
> >> +                                                ControlSerializer *cs)
> >> +{
> >> +       return deserialize(data.cbegin(), data.end(), cs);
> >> +}
> >> +
> >> +template<>
> >> +MetadataListPlan
> >> +IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
> >> +                                                const std::vector<SharedFD> &fds,
> >> +                                                ControlSerializer *cs)
> >> +{
> >> +       return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
> >> +}
> >> +
> >>   #endif /* __DOXYGEN__ */
> >>
> >>   } /* namespace libcamera */
> >> diff --git a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> >> index 3942e5708..b3774cd64 100644
> >> --- a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> >> +++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
> >> @@ -21,6 +21,7 @@
> >>   #include <libcamera/controls.h>
> >>   #include <libcamera/framebuffer.h>
> >>   #include <libcamera/geometry.h>
> >> +#include <libcamera/metadata_list_plan.h>
> >>
> >>   #include <libcamera/ipa/ipa_interface.h>
> >>
> >> --
> >> 2.50.1
> >>
>

Patch
diff mbox series

diff --git a/include/libcamera/ipa/core.mojom b/include/libcamera/ipa/core.mojom
index bce797245..754e4065c 100644
--- a/include/libcamera/ipa/core.mojom
+++ b/include/libcamera/ipa/core.mojom
@@ -83,6 +83,7 @@  module libcamera;
 [skipSerdes, skipHeader] struct ControlInfoMap {};
 [skipSerdes, skipHeader] struct ControlList {};
 [skipSerdes, skipHeader] struct SharedFD {};
+[skipSerdes, skipHeader] struct MetadataListPlan {};
 
 [skipHeader] struct Point {
 	int32 x;
diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
index 0537f785b..26090f658 100644
--- a/src/libcamera/ipa_data_serializer.cpp
+++ b/src/libcamera/ipa_data_serializer.cpp
@@ -11,6 +11,8 @@ 
 
 #include <libcamera/base/log.h>
 
+#include <libcamera/metadata_list_plan.h>
+
 #include "libcamera/internal/byte_stream_buffer.h"
 
 /**
@@ -620,6 +622,95 @@  IPADataSerializer<FrameBuffer::Plane>::deserialize(const std::vector<uint8_t> &d
 	return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
 }
 
+template<>
+std::tuple<std::vector<uint8_t>, std::vector<SharedFD>>
+IPADataSerializer<MetadataListPlan>::serialize(const MetadataListPlan &data,
+					       [[maybe_unused]] ControlSerializer *cs)
+{
+	std::vector<uint8_t> dataVec;
+
+	appendPOD<uint32_t>(dataVec, data.size());
+
+	for (const auto &[tag, e] : data) {
+		appendPOD<uint32_t>(dataVec, tag);
+		appendPOD<uint32_t>(dataVec, e.size);
+		appendPOD<uint32_t>(dataVec, e.alignment);
+		appendPOD<uint32_t>(dataVec, e.numElements);
+		appendPOD<uint32_t>(dataVec, e.type);
+		appendPOD<uint8_t>(dataVec, e.isArray);
+	}
+
+	return { dataVec, {} };
+}
+
+template<>
+MetadataListPlan
+IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+						 std::vector<uint8_t>::const_iterator dataEnd,
+						 [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsBegin,
+						 [[maybe_unused]] std::vector<SharedFD>::const_iterator fdsEnd,
+						 [[maybe_unused]] ControlSerializer *cs)
+{
+	MetadataListPlan ret;
+	std::size_t offset = 0;
+
+	auto n = readPOD<uint32_t>(dataBegin, 0, dataEnd);
+	offset += sizeof(n);
+
+	while (n--) {
+		auto tag = readPOD<uint32_t>(dataBegin, offset, dataEnd);
+		offset += sizeof(tag);
+
+		auto size = readPOD<uint32_t>(dataBegin, offset, dataEnd);
+		offset += sizeof(size);
+
+		auto alignment = readPOD<uint32_t>(dataBegin, offset, dataEnd);
+		offset += sizeof(alignment);
+
+		auto numElements = readPOD<uint32_t>(dataBegin, offset, dataEnd);
+		offset += sizeof(numElements);
+
+		auto type = readPOD<uint32_t>(dataBegin, offset, dataEnd);
+		offset += sizeof(type);
+
+		auto isArray = readPOD<uint8_t>(dataBegin, offset, dataEnd);
+		offset += sizeof(isArray);
+
+		[[maybe_unused]] bool ok = ret.set(tag,
+						   size, alignment,
+						   numElements, static_cast<ControlType>(type), isArray);
+		ASSERT(ok);
+	}
+
+	return ret;
+}
+
+template<>
+MetadataListPlan
+IPADataSerializer<MetadataListPlan>::deserialize(std::vector<uint8_t>::const_iterator dataBegin,
+						 std::vector<uint8_t>::const_iterator dataEnd,
+						 ControlSerializer *cs)
+{
+	return deserialize(dataBegin, dataEnd, {}, {}, cs);
+}
+
+template<>
+MetadataListPlan
+IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
+						 ControlSerializer *cs)
+{
+	return deserialize(data.cbegin(), data.end(), cs);
+}
+
+template<>
+MetadataListPlan
+IPADataSerializer<MetadataListPlan>::deserialize(const std::vector<uint8_t> &data,
+						 const std::vector<SharedFD> &fds,
+						 ControlSerializer *cs)
+{
+	return deserialize(data.cbegin(), data.end(), fds.cbegin(), fds.end(), cs);
+}
+
 #endif /* __DOXYGEN__ */
 
 } /* namespace libcamera */
diff --git a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
index 3942e5708..b3774cd64 100644
--- a/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
@@ -21,6 +21,7 @@ 
 #include <libcamera/controls.h>
 #include <libcamera/framebuffer.h>
 #include <libcamera/geometry.h>
+#include <libcamera/metadata_list_plan.h>
 
 #include <libcamera/ipa/ipa_interface.h>