diff --git a/include/libcamera/internal/ipa_data_serializer.h b/include/libcamera/internal/ipa_data_serializer.h
new file mode 100644
index 00000000..368a398b
--- /dev/null
+++ b/include/libcamera/internal/ipa_data_serializer.h
@@ -0,0 +1,816 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_data_serializer.h - Image Processing Algorithm data serializer
+ */
+#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_H__
+#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_H__
+
+#include <deque>
+#include <iostream>
+#include <tuple>
+#include <vector>
+
+#include <libcamera/buffer.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/geometry.h>
+#include <libcamera/ipa/ipa_interface.h>
+
+#include "libcamera/internal/byte_stream_buffer.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/control_serializer.h"
+#include "libcamera/internal/log.h"
+
+#include <iomanip>
+
+template<typename T> std::ostream &operator<<(std::ostream &stream, const std::vector<T> &vec)
+{
+	stream << "{ ";
+	for (const T &v : vec)
+		stream << std::hex << std::setfill('0') << std::setw(2) << static_cast<unsigned int>(v) << ", ";
+
+	stream << " }";
+	return stream;
+}
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPADataSerializer)
+
+static void appendUint32(std::vector<uint8_t> &vec, uint32_t val)
+{
+	for (int i = 0; i < 4; i++)
+		vec.push_back(static_cast<uint8_t>((val >> 8*i) & 0xff));
+}
+
+static void appendUint64(std::vector<uint8_t> &vec, uint64_t val)
+{
+	for (int i = 0; i < 8; i++)
+		vec.push_back(static_cast<uint8_t>((val >> 8*i) & 0xff));
+}
+
+static uint32_t extractUint32(std::vector<uint8_t>::iterator it)
+{
+	uint32_t ret = 0;
+	for (int i = 0; i < 4; i++)
+		ret |= *(it + i) << 8*i;
+	return ret;
+}
+
+static uint64_t extractUint64(std::vector<uint8_t>::iterator it)
+{
+	uint32_t ret = 0;
+	for (int i = 0; i < 8; i++)
+		ret |= *(it + i) << 8*i;
+	return ret;
+}
+
+template<typename T>
+class IPADataSerializer
+{
+};
+
+template<typename V>
+class IPADataSerializer<std::vector<V>>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const std::vector<V> &data, ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+		std::vector<int32_t> fds_vec;
+
+		// serialize the length
+		uint32_t vec_len = data.size();
+		appendUint32(data_vec, vec_len);
+
+		// serialize the members
+		for (auto it = data.begin(); it != data.end(); ++it) {
+			std::vector<uint8_t> dvec;
+			std::vector<int32_t> fvec;
+
+			std::tie(dvec, fvec) =
+				IPADataSerializer<V>::serialize(*it, cs);
+
+			appendUint32(data_vec, dvec.size());
+			appendUint32(data_vec, fvec.size());
+
+			data_vec.insert(data_vec.end(), dvec.begin(), dvec.end());
+			fds_vec.insert(fds_vec.end(), fvec.begin(), fvec.end());
+		}
+
+		return {data_vec, fds_vec};
+	}
+
+	static std::vector<V> deserialize(std::vector<uint8_t> &data, ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<std::vector<V>>::deserialize(data.begin(), data.end(), cs);
+	}
+
+	static std::vector<V> deserialize(std::vector<uint8_t>::iterator it1,
+					  std::vector<uint8_t>::iterator it2,
+					  ControlSerializer *cs = nullptr)
+	{
+		std::vector<int32_t> fds;
+		return IPADataSerializer<std::vector<V>>::deserialize(it1, it2,
+								      fds.begin(), fds.end(),
+								      cs);
+	}
+
+	static std::vector<V> deserialize(std::vector<uint8_t> &data, std::vector<int32_t> &fds,
+					  ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<std::vector<V>>::deserialize(data.begin(), data.end(),
+								      fds.begin(), fds.end(),
+								      cs);
+	}
+
+	static std::vector<V> deserialize(std::vector<uint8_t>::iterator data_it1,
+					  [[maybe_unused]] std::vector<uint8_t>::iterator data_it2,
+					  std::vector<int32_t>::iterator fds_it1,
+					  [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+					  ControlSerializer *cs = nullptr)
+	{
+		uint32_t vec_len = extractUint32(data_it1);
+		std::vector<V> ret(vec_len);
+
+		std::vector<uint8_t>::iterator data_it = data_it1 + 4;
+		std::vector<int32_t>::iterator fd_it = fds_it1;
+		for (uint32_t i = 0; i < vec_len; i++) {
+			uint32_t sizeof_data = extractUint32(data_it);
+			uint32_t sizeof_fds  = extractUint32(data_it + 4);
+
+			ret[i] = IPADataSerializer<V>::deserialize(data_it + 8,
+								   data_it + 8 + sizeof_data,
+								   fd_it,
+								   fd_it + sizeof_fds,
+								   cs);
+
+			data_it += 8 + sizeof_data;
+			fd_it += sizeof_fds;
+		}
+
+		return ret;
+	}
+};
+
+template<typename K, typename V>
+class IPADataSerializer<std::map<K, V>>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const std::map<K, V> &data, ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+		std::vector<int32_t> fds_vec;
+
+		// serialize the length
+		uint32_t map_len = data.size();
+		appendUint32(data_vec, map_len);
+
+		// serialize the members
+		for (auto it = data.begin(); it != data.end(); ++it) {
+			std::vector<uint8_t> dvec;
+			std::vector<int32_t> fvec;
+
+			std::tie(dvec, fvec) =
+				IPADataSerializer<K>::serialize(it->first, cs);
+
+			appendUint32(data_vec, dvec.size());
+			appendUint32(data_vec, fvec.size());
+
+			data_vec.insert(data_vec.end(), dvec.begin(), dvec.end());
+			fds_vec.insert(fds_vec.end(), fvec.begin(), fvec.end());
+
+			std::tie(dvec, fvec) =
+				IPADataSerializer<V>::serialize(it->second, cs);
+
+			appendUint32(data_vec, dvec.size());
+			appendUint32(data_vec, fvec.size());
+
+			data_vec.insert(data_vec.end(), dvec.begin(), dvec.end());
+			fds_vec.insert(fds_vec.end(), fvec.begin(), fvec.end());
+		}
+
+		return {data_vec, fds_vec};
+	}
+
+	static std::map<K, V> deserialize(std::vector<uint8_t> &data, ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<std::map<K, V>>::deserialize(data.begin(), data.end(), cs);
+	}
+
+	static std::map<K, V> deserialize(std::vector<uint8_t>::iterator it1,
+					  std::vector<uint8_t>::iterator it2,
+					  ControlSerializer *cs = nullptr)
+	{
+		std::vector<int32_t> fds;
+		return IPADataSerializer<std::map<K, V>>::deserialize(it1, it2,
+								      fds.begin(), fds.end(),
+								      cs);
+	}
+
+	static std::map<K, V> deserialize(std::vector<uint8_t> &data, std::vector<int32_t> &fds,
+					  ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<std::map<K, V>>::deserialize(data.begin(), data.end(),
+								      fds.begin(), fds.end(),
+								      cs);
+	}
+
+	static std::map<K, V> deserialize(std::vector<uint8_t>::iterator data_it1,
+					  [[maybe_unused]] std::vector<uint8_t>::iterator data_it2,
+					  std::vector<int32_t>::iterator fds_it1,
+					  [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+					  ControlSerializer *cs = nullptr)
+	{
+		std::map<K, V> ret;
+
+		uint32_t map_len = extractUint32(data_it1);
+
+		std::vector<uint8_t>::iterator data_it = data_it1 + 4;
+		std::vector<int32_t>::iterator fd_it = fds_it1;
+		for (uint32_t i = 0; i < map_len; i++) {
+			uint32_t sizeof_data = extractUint32(data_it);
+			uint32_t sizeof_fds  = extractUint32(data_it + 4);
+
+			K key = IPADataSerializer<K>::deserialize(data_it + 8,
+								  data_it + 8 + sizeof_data,
+								  fd_it,
+								  fd_it + sizeof_fds,
+								  cs);
+
+			data_it += 8 + sizeof_data;
+			fd_it += sizeof_fds;
+			sizeof_data = extractUint32(data_it);
+			sizeof_fds  = extractUint32(data_it + 4);
+
+			const V value = IPADataSerializer<V>::deserialize(data_it + 8,
+									  data_it + 8 + sizeof_data,
+									  fd_it,
+									  fd_it + sizeof_fds,
+									  cs);
+			ret.insert({key, value});
+
+			data_it += 8 + sizeof_data;
+			fd_it += sizeof_fds;
+		}
+
+		return ret;
+	}
+};
+
+// TODO implement this for all primitives
+template<>
+class IPADataSerializer<unsigned int>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const unsigned int data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+		appendUint32(data_vec, data);
+
+		return {data_vec, {}};
+	}
+
+	static unsigned int deserialize(std::vector<uint8_t> &data,
+					[[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<unsigned int>::deserialize(data.begin(), data.end());
+	}
+
+	static unsigned int deserialize(std::vector<uint8_t>::iterator it1,
+					[[maybe_unused]] std::vector<uint8_t>::iterator it2,
+					[[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return extractUint32(it1);
+	}
+
+	static unsigned int deserialize(std::vector<uint8_t> &data,
+					[[maybe_unused]] std::vector<int32_t> &fds,
+					[[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<unsigned int>::deserialize(data.begin(), data.end());
+	}
+
+	static unsigned int deserialize(std::vector<uint8_t>::iterator data_it1,
+					std::vector<uint8_t>::iterator data_it2,
+					[[maybe_unused]] std::vector<int32_t>::iterator fds_it1,
+					[[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+					[[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<unsigned int>::deserialize(data_it1, data_it2);
+	}
+};
+
+template<>
+class IPADataSerializer<FileDescriptor>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const FileDescriptor &data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec = { data.isValid() };
+		std::vector<int32_t> fd_vec;
+		if (data.isValid())
+			fd_vec.push_back(data.fd());
+
+		return {data_vec, fd_vec};
+	}
+
+	static FileDescriptor deserialize(std::vector<uint8_t> &data, std::vector<int32_t> &fds,
+					  [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<FileDescriptor>::deserialize(data.begin(), data.end(),
+								      fds.begin(), fds.end());
+	}
+
+	static FileDescriptor deserialize(std::vector<uint8_t>::iterator data_it1,
+					  std::vector<uint8_t>::iterator data_it2,
+					  std::vector<int32_t>::iterator fds_it1,
+					  std::vector<int32_t>::iterator fds_it2,
+					  [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		if (std::distance(data_it1, data_it2) < 1)
+			LOG(IPADataSerializer, Fatal)
+				<< "Invalid data to deserialize FileDescriptor";
+
+		bool valid = *data_it1;
+
+		if (valid && std::distance(fds_it1, fds_it2) < 1)
+			LOG(IPADataSerializer, Fatal)
+				<< "Invalid fds to deserialize FileDescriptor";
+
+		return valid ? FileDescriptor(*fds_it1) : FileDescriptor();
+	}
+};
+
+template<>
+class IPADataSerializer<IPASettings>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const IPASettings &data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec(data.configurationFile.begin(),
+					      data.configurationFile.end());
+
+		return {data_vec, {}};
+	}
+
+	static IPASettings deserialize(std::vector<uint8_t> &data,
+				       [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPASettings>::deserialize(data.begin(), data.end());
+	}
+
+	static IPASettings deserialize(std::vector<uint8_t>::iterator it1,
+				       std::vector<uint8_t>::iterator it2,
+				       [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::string str(it1, it2);
+
+		IPASettings ret;
+		ret.configurationFile = str;
+
+		return ret;
+	}
+
+	static IPASettings deserialize(std::vector<uint8_t> &data,
+				       [[maybe_unused]] std::vector<int32_t> &fds,
+				       [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPASettings>::deserialize(data.begin(), data.end());
+	}
+
+	static IPASettings deserialize(std::vector<uint8_t>::iterator data_it1,
+				       std::vector<uint8_t>::iterator data_it2,
+				       [[maybe_unused]] std::vector<int32_t>::iterator fds_it1,
+				       [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+				       [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPASettings>::deserialize(data_it1, data_it2);
+	}
+};
+
+template<>
+class IPADataSerializer<CameraSensorInfo>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const CameraSensorInfo &data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+
+		uint32_t str_len = data.model.size();
+		appendUint32(data_vec, str_len);
+
+		data_vec.insert(data_vec.end(), data.model.begin(), data.model.end());
+
+		appendUint32(data_vec, data.bitsPerPixel);
+
+		appendUint32(data_vec, data.activeAreaSize.width);
+		appendUint32(data_vec, data.activeAreaSize.height);
+
+		appendUint32(data_vec, static_cast<uint32_t>(data.analogCrop.x));
+		appendUint32(data_vec, static_cast<uint32_t>(data.analogCrop.y));
+		appendUint32(data_vec, data.analogCrop.width);
+		appendUint32(data_vec, data.analogCrop.height);
+
+		appendUint32(data_vec, data.outputSize.width);
+		appendUint32(data_vec, data.outputSize.height);
+
+		appendUint64(data_vec, data.pixelRate);
+
+		appendUint32(data_vec, data.lineLength);
+
+		return {data_vec, {}};
+	}
+
+	static CameraSensorInfo deserialize(std::vector<uint8_t> &data,
+					    [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<CameraSensorInfo>::deserialize(data.begin(), data.end());
+	}
+
+	static CameraSensorInfo deserialize(std::vector<uint8_t>::iterator it1,
+					    [[maybe_unused]] std::vector<uint8_t>::iterator it2,
+					    [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		CameraSensorInfo ret;
+
+		uint32_t str_len = extractUint32(it1);
+		std::string str(it1 + 4, it1 + 4 + str_len);
+		ret.model = str;
+
+		std::vector<uint8_t>::iterator it = it1 + 4 + str_len;
+
+		ret.bitsPerPixel = extractUint32(it);
+
+		ret.activeAreaSize.width = extractUint32(it + 4);
+		ret.activeAreaSize.height = extractUint32(it + 8);
+
+		ret.analogCrop.x = static_cast<int32_t>(extractUint32(it + 12));
+		ret.analogCrop.y = static_cast<int32_t>(extractUint32(it + 16));
+		ret.analogCrop.width = extractUint32(it + 20);
+		ret.analogCrop.height = extractUint32(it + 24);
+
+		ret.outputSize.width = extractUint32(it + 28);
+		ret.outputSize.height = extractUint32(it + 32);
+
+		ret.pixelRate = extractUint64(it + 36);
+
+		ret.lineLength = extractUint64(it + 44);
+
+		return ret;
+	}
+
+	static CameraSensorInfo deserialize(std::vector<uint8_t> &data,
+					    [[maybe_unused]] std::vector<int32_t> &fds,
+					    [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<CameraSensorInfo>::deserialize(data.begin(), data.end());
+	}
+
+	static CameraSensorInfo deserialize(std::vector<uint8_t>::iterator data_it1,
+					    std::vector<uint8_t>::iterator data_it2,
+					    [[maybe_unused]] std::vector<int32_t>::iterator fds_it1,
+					    [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+					    [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<CameraSensorInfo>::deserialize(data_it1, data_it2);
+	}
+};
+
+template<>
+class IPADataSerializer<IPAStream>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const IPAStream &data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+
+		appendUint32(data_vec, data.pixelFormat);
+
+		appendUint32(data_vec, data.size.width);
+		appendUint32(data_vec, data.size.height);
+
+		return {data_vec, {}};
+	}
+
+	static IPAStream deserialize(std::vector<uint8_t> &data,
+				     [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPAStream>::deserialize(data.begin(), data.end());
+	}
+
+	static IPAStream deserialize(std::vector<uint8_t>::iterator it1,
+				     [[maybe_unused]] std::vector<uint8_t>::iterator it2,
+				     [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		IPAStream ret;
+
+		ret.pixelFormat = extractUint32(it1);
+
+		ret.size.width = extractUint32(it1 + 4);
+		ret.size.height = extractUint32(it1 + 8);
+
+		return ret;
+	}
+
+	static IPAStream deserialize(std::vector<uint8_t> &data,
+				     [[maybe_unused]] std::vector<int32_t> &fds,
+				     [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPAStream>::deserialize(data.begin(), data.end());
+	}
+
+	static IPAStream deserialize(std::vector<uint8_t>::iterator data_it1,
+				     std::vector<uint8_t>::iterator data_it2,
+				     [[maybe_unused]] std::vector<int32_t>::iterator fds_it1,
+				     [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+				     [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPAStream>::deserialize(data_it1, data_it2);
+	}
+};
+
+template<>
+class IPADataSerializer<ControlList>
+{
+public:
+	// map arg will be generated, since it's per-pipeline anyway
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const ControlList &data, const ControlInfoMap &map,
+		  ControlSerializer *cs)
+	{
+		if (!cs)
+			LOG(IPADataSerializer, Fatal)
+				<< "ControlSerializer not provided for serialization of ControlList";
+
+		size_t size = cs->binarySize(map);
+		std::vector<uint8_t> infoData(size);
+		ByteStreamBuffer buffer(infoData.data(), infoData.size());
+		int ret = cs->serialize(map, buffer);
+
+		if (ret < 0 || buffer.overflow()) {
+			std::cerr << "Failed to serialize ControlList's ControlInfoMap" << std::endl;
+			return {{}, {}};
+		}
+
+		size = cs->binarySize(data);
+		std::vector<uint8_t> listData(size);
+		buffer = ByteStreamBuffer(listData.data(), listData.size());
+		ret = cs->serialize(data, buffer);
+
+		if (ret < 0 || buffer.overflow()) {
+			std::cerr << "Failed to serialize ControlList" << std::endl;
+			return {{}, {}};
+		}
+
+		std::vector<uint8_t> data_vec;
+		appendUint32(data_vec, infoData.size());
+		appendUint32(data_vec, listData.size());
+		data_vec.insert(data_vec.end(), infoData.begin(), infoData.end());
+		data_vec.insert(data_vec.end(), listData.begin(), listData.end());
+
+		return {data_vec, {}};
+	}
+
+	static ControlList deserialize(std::vector<uint8_t> &data, ControlSerializer *cs)
+	{
+		return IPADataSerializer<ControlList>::deserialize(data.begin(), data.end(), cs);
+	}
+
+	static ControlList deserialize(std::vector<uint8_t>::iterator it1,
+				       [[maybe_unused]] std::vector<uint8_t>::iterator it2,
+				       ControlSerializer *cs)
+	{
+		if (!cs)
+			LOG(IPADataSerializer, Fatal)
+				<< "ControlSerializer not provided for deserialization of ControlList";
+
+		uint32_t infoDataSize = extractUint32(it1);
+		uint32_t listDataSize = extractUint32(it1 + 4);
+
+		std::vector<uint8_t>::iterator it = it1 + 8;
+
+		std::vector<uint8_t> infoData(it, it + infoDataSize);
+		std::vector<uint8_t> listData(it + infoDataSize, it + infoDataSize + listDataSize);
+
+		ByteStreamBuffer buffer(const_cast<const uint8_t *>(infoData.data()), infoData.size());
+		ControlInfoMap map = cs->deserialize<ControlInfoMap>(buffer);
+		if (map.empty() || buffer.overflow()) {
+			std::cerr << "Failed to deserialize ControlLists's ControlInfoMap" << std::endl;
+			return ControlList();
+		}
+
+		buffer = ByteStreamBuffer(const_cast<const uint8_t *>(listData.data()), listData.size());
+		ControlList list = cs->deserialize<ControlList>(buffer);
+		if (buffer.overflow())
+			std::cerr << "Failed to deserialize ControlList: buffer overflow" << std::endl;
+		if (list.empty())
+			std::cerr << "Failed to deserialize ControlList: empty list" << std::endl;
+
+		return list;
+	}
+
+	static ControlList deserialize(std::vector<uint8_t> &data,
+				       [[maybe_unused]] std::vector<int32_t> &fds,
+				       ControlSerializer *cs)
+	{
+		return IPADataSerializer<ControlList>::deserialize(data.begin(), data.end(), cs);
+	}
+
+	static ControlList deserialize(std::vector<uint8_t>::iterator data_it1,
+				       std::vector<uint8_t>::iterator data_it2,
+				       [[maybe_unused]] std::vector<int32_t>::iterator fds_it1,
+				       [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+				       ControlSerializer *cs)
+	{
+		return IPADataSerializer<ControlList>::deserialize(data_it1, data_it2, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<const ControlInfoMap>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const ControlInfoMap &map, ControlSerializer *cs)
+	{
+		if (!cs)
+			LOG(IPADataSerializer, Fatal)
+				<< "ControlSerializer not provided for serialization of ControlInfoMap";
+
+		size_t size = cs->binarySize(map);
+		std::vector<uint8_t> infoData(size);
+		ByteStreamBuffer buffer(infoData.data(), infoData.size());
+		int ret = cs->serialize(map, buffer);
+
+		if (ret < 0 || buffer.overflow())
+			return {{}, {}};
+
+		std::vector<uint8_t> data_vec;
+		appendUint32(data_vec, infoData.size());
+		data_vec.insert(data_vec.end(), infoData.begin(), infoData.end());
+
+		return {data_vec, {}};
+	}
+
+	static const ControlInfoMap deserialize(std::vector<uint8_t> &data,
+						ControlSerializer *cs)
+	{
+		return IPADataSerializer<const ControlInfoMap>::deserialize(data.begin(), data.end(), cs);
+	}
+
+	static const ControlInfoMap deserialize(std::vector<uint8_t>::iterator it1,
+						[[maybe_unused]] std::vector<uint8_t>::iterator it2,
+						ControlSerializer *cs)
+	{
+		if (!cs)
+			LOG(IPADataSerializer, Fatal)
+				<< "ControlSerializer not provided for deserialization of ControlInfoMap";
+
+		uint32_t infoDataSize = extractUint32(it1);
+
+		std::vector<uint8_t>::iterator it = it1 + 4;
+
+		std::vector<uint8_t> infoData(it, it + infoDataSize);
+
+		ByteStreamBuffer buffer(const_cast<const uint8_t *>(infoData.data()), infoData.size());
+		const ControlInfoMap map = cs->deserialize<ControlInfoMap>(buffer);
+
+		/*
+		ControlInfoMap::Map ctrls;
+		for (auto pair : map)
+			ctrls.emplace(controls::controls.at(pair.first->id()),
+				      pair.second);
+
+		ControlInfoMap ret = std::move(ctrls);
+		return ret;
+		*/
+
+		return map;
+	}
+
+	static const ControlInfoMap deserialize(std::vector<uint8_t> &data,
+						[[maybe_unused]] std::vector<int32_t> &fds,
+						ControlSerializer *cs)
+	{
+		return IPADataSerializer<const ControlInfoMap>::deserialize(data.begin(), data.end(), cs);
+	}
+
+	static const ControlInfoMap deserialize(std::vector<uint8_t>::iterator data_it1,
+					  std::vector<uint8_t>::iterator data_it2,
+					  [[maybe_unused]] std::vector<int32_t>::iterator fds_it1,
+					  [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+					  ControlSerializer *cs)
+	{
+		return IPADataSerializer<const ControlInfoMap>::deserialize(data_it1, data_it2, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<FrameBuffer::Plane>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const FrameBuffer::Plane &data, [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+		std::vector<int32_t> fds_vec;
+
+		// fd
+		std::vector<uint8_t> fdBuf;
+		std::vector<int32_t> fdFds;
+		std::tie(fdBuf, fdFds) =
+			IPADataSerializer<FileDescriptor>::serialize(data.fd);
+		data_vec.insert(data_vec.end(), fdBuf.begin(), fdBuf.end());
+		fds_vec.insert(fds_vec.end(), fdFds.begin(), fdFds.end());
+
+		// length
+		appendUint32(data_vec, data.length);
+
+		return {data_vec, fds_vec};
+	}
+
+	static FrameBuffer::Plane deserialize(std::vector<uint8_t> &data,
+					      std::vector<int32_t> &fds,
+					      ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<FrameBuffer::Plane>::deserialize(data.begin(), data.end(),
+									  fds.begin(), fds.end(),
+									  cs);
+	}
+
+	static FrameBuffer::Plane deserialize(std::vector<uint8_t>::iterator data_it1,
+					      [[maybe_unused]] std::vector<uint8_t>::iterator data_it2,
+					      std::vector<int32_t>::iterator fds_it1,
+					      [[maybe_unused]] std::vector<int32_t>::iterator fds_it2,
+					      [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		FrameBuffer::Plane ret;
+
+		ret.fd = IPADataSerializer<FileDescriptor>::deserialize(data_it1, data_it1 + 1,
+									fds_it1, fds_it1 + 1);
+		ret.length = extractUint32(data_it1 + 1);
+
+		return ret;
+	}
+};
+
+
+template<>
+class IPADataSerializer<IPABuffer>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const IPABuffer &data, ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data_vec;
+
+		appendUint32(data_vec, data.id);
+
+		std::vector<uint8_t> planes_data_vec;
+		std::vector<int32_t> planes_fds_vec;
+		std::tie(planes_data_vec, planes_fds_vec) =
+			IPADataSerializer<std::vector<FrameBuffer::Plane>>::serialize(data.planes, cs);
+
+		data_vec.insert(data_vec.end(), planes_data_vec.begin(), planes_data_vec.end());
+
+		return {data_vec, planes_fds_vec};
+	}
+
+	static IPABuffer deserialize(std::vector<uint8_t> &data,
+				     std::vector<int32_t> &fds,
+				     ControlSerializer *cs = nullptr)
+	{
+		return IPADataSerializer<IPABuffer>::deserialize(data.begin(), data.end(),
+								 fds.begin(), fds.end(), cs);
+	}
+
+	static IPABuffer deserialize(std::vector<uint8_t>::iterator data_it1,
+				     std::vector<uint8_t>::iterator data_it2,
+				     std::vector<int32_t>::iterator fds_it1,
+				     std::vector<int32_t>::iterator fds_it2,
+				     ControlSerializer *cs = nullptr)
+	{
+		IPABuffer ret;
+
+		ret.id = extractUint32(data_it1);
+
+		ret.planes =
+			IPADataSerializer<std::vector<FrameBuffer::Plane>>::deserialize(
+				data_it1 + 4, data_it2, fds_it1, fds_it2, cs);
+
+		return ret;
+	}
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_H__ */
diff --git a/include/libcamera/ipa/raspberrypi_serializer.h b/include/libcamera/ipa/raspberrypi_serializer.h
new file mode 100644
index 00000000..01d69986
--- /dev/null
+++ b/include/libcamera/ipa/raspberrypi_serializer.h
@@ -0,0 +1,487 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * raspberrypi_serializer.h - Image Processing Algorithm data serializer for raspberry pi
+ */
+
+// automatically generated by custom compiler
+
+#include <libcamera/ipa/raspberrypi.h>
+#include <libcamera/ipa/raspberrypi_wrapper.h>
+
+#include "libcamera/internal/ipa_data_serializer.h"
+
+#ifndef __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_RASPBERRYPI_H__
+#define __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_RASPBERRYPI_H__
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(IPADataSerializer)
+
+template<>
+class IPADataSerializer<RPiStaggeredWritePayload>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiStaggeredWritePayload data,
+		  [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> ret_data;
+
+		// scalar gainDelay_
+		appendUint32(ret_data, data.gainDelay_);
+
+		// scalar exposureDelay_
+		appendUint32(ret_data, data.exposureDelay_);
+
+		// scalar sensorMetadata_
+		appendUint32(ret_data, data.sensorMetadata_);
+
+		return {ret_data, {}};
+	}
+
+	static RPiStaggeredWritePayload deserialize(std::vector<uint8_t> &data,
+						    [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		RPiStaggeredWritePayload ret;
+		std::vector<uint8_t>::iterator m = data.begin();
+
+		// scalar gainDelay_
+		ret.gainDelay_ = extractUint32(m);
+		m += 4;
+
+		// scalar exposureDelay_
+		ret.exposureDelay_ = extractUint32(m);
+		m += 4;
+
+		// scalar sensorMetadata_
+		ret.sensorMetadata_ = extractUint32(m);
+
+		return ret;
+	}
+
+	static RPiStaggeredWritePayload deserialize(std::vector<uint8_t>::iterator it1,
+						    std::vector<uint8_t>::iterator it2,
+						    ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data(it1, it2);
+		return IPADataSerializer<RPiStaggeredWritePayload>::deserialize(data, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<RPiIspPreparePayload>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiIspPreparePayload data,
+		  [[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> ret_data;
+
+		// scalar embeddedbufferId_
+		appendUint32(ret_data, data.embeddedbufferId_);
+
+		// scalar bayerbufferId_
+		appendUint32(ret_data, data.bayerbufferId_);
+
+		return {ret_data, {}};
+	}
+
+	static RPiIspPreparePayload deserialize(std::vector<uint8_t> &data,
+						[[maybe_unused]] ControlSerializer *cs = nullptr)
+	{
+		RPiIspPreparePayload ret;
+		std::vector<uint8_t>::iterator m = data.begin();
+
+		// scalar embeddedbufferId_
+		ret.embeddedbufferId_ = extractUint32(m);
+		m += 4;
+
+		// scalar bayerbufferId_
+		ret.bayerbufferId_ = extractUint32(m);
+
+		return ret;
+	}
+
+	static RPiIspPreparePayload deserialize(std::vector<uint8_t>::iterator it1,
+						std::vector<uint8_t>::iterator it2,
+						ControlSerializer *cs = nullptr)
+	{
+		std::vector<uint8_t> data(it1, it2);
+		return IPADataSerializer<RPiIspPreparePayload>::deserialize(data, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<RPiStatsCompletePayload>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiStatsCompletePayload data,
+		  ControlSerializer *cs)
+	{
+		std::vector<uint8_t> ret_data;
+
+		// scalar bufferId_
+		appendUint32(ret_data, data.bufferId_);
+
+		// ControlList controls_
+		if (data.controls_.size() > 0) {
+			std::vector<uint8_t> controls;
+			std::tie(controls, std::ignore) =
+				IPADataSerializer<ControlList>::serialize(data.controls_,
+						data.controls_.infoMap() ? *data.controls_.infoMap() : RPiControls,
+						cs);
+			appendUint32(ret_data, controls.size());
+			ret_data.insert(ret_data.end(), controls.begin(), controls.end());
+		} else {
+			appendUint32(ret_data, 0);
+		}
+
+
+		return {ret_data, {}};
+	}
+
+	static RPiStatsCompletePayload deserialize(std::vector<uint8_t> &data,
+						   ControlSerializer *cs)
+	{
+		RPiStatsCompletePayload ret;
+		std::vector<uint8_t>::iterator m = data.begin();
+
+		// scalar bufferId_
+		ret.bufferId_ = extractUint32(m);
+		m += 4;
+
+		// ControlList controls_
+		size_t controlsSize = extractUint32(m);
+		if (controlsSize > 0)
+			ret.controls_ =
+				IPADataSerializer<ControlList>::deserialize(m + 4, m + 4 + controlsSize, cs);
+
+		return ret;
+	}
+
+	static RPiStatsCompletePayload deserialize(std::vector<uint8_t>::iterator it1,
+						   std::vector<uint8_t>::iterator it2,
+						   ControlSerializer *cs)
+	{
+		std::vector<uint8_t> data(it1, it2);
+		return IPADataSerializer<RPiStatsCompletePayload>::deserialize(data, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<RPiConfigurePayload>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiConfigurePayload data,
+		  ControlSerializer *cs)
+	{
+		std::vector<uint8_t> ret_data;
+		std::vector<int32_t> ret_fds;
+
+		// scalar op_
+		appendUint32(ret_data, data.op_);
+
+		// fd lsTableHandle_
+		std::vector<uint8_t> lsTableHandle;
+		std::vector<int32_t> lsTableHandleFds;
+		std::tie(lsTableHandle, lsTableHandleFds) =
+			IPADataSerializer<FileDescriptor>::serialize(data.lsTableHandle_);
+		ret_data.insert(ret_data.end(), lsTableHandle.begin(), lsTableHandle.end());
+		ret_fds.insert(ret_fds.end(), lsTableHandleFds.begin(), lsTableHandleFds.end());
+
+		// scalar lsTableHandleStatic_
+		appendUint32(ret_data, static_cast<uint32_t>(data.lsTableHandleStatic_));
+
+		// struct staggeredWriteResult_
+		std::vector<uint8_t> staggeredWriteResult;
+		std::tie(staggeredWriteResult, std::ignore) =
+			IPADataSerializer<RPiStaggeredWritePayload>::serialize(data.staggeredWriteResult_, cs);
+		appendUint32(ret_data, staggeredWriteResult.size());
+		ret_data.insert(ret_data.end(), staggeredWriteResult.begin(), staggeredWriteResult.end());
+
+		// ControlList controls_
+		if (data.controls_.size() > 0) {
+			std::vector<uint8_t> controls;
+			std::tie(controls, std::ignore) =
+				IPADataSerializer<ControlList>::serialize(data.controls_,
+					data.controls_.infoMap() ? *data.controls_.infoMap() : RPiControls,
+					cs);
+			appendUint32(ret_data, controls.size());
+			ret_data.insert(ret_data.end(), controls.begin(), controls.end());
+		} else {
+			appendUint32(ret_data, 0);
+		}
+
+		// scalar bufferFd_
+		appendUint32(ret_data, static_cast<uint32_t>(data.bufferFd_));
+
+		return {ret_data, ret_fds};
+	}
+
+	static RPiConfigurePayload deserialize(std::vector<uint8_t> &data,
+					       std::vector<int32_t> &fds,
+					       ControlSerializer *cs)
+	{
+		RPiConfigurePayload ret;
+		std::vector<uint8_t>::iterator m = data.begin();
+		std::vector<int32_t>::iterator n = fds.begin();
+
+		// scalar op_
+		ret.op_ = static_cast<RPiConfigParameters>(extractUint32(m));
+		m += 4;
+
+		// fd lsTableHandle_
+		ret.lsTableHandle_ = IPADataSerializer<FileDescriptor>::deserialize(m, m + 1, n, n + 1);
+		m += 1;
+		n += ret.lsTableHandle_.isValid() ? 1 : 0;
+
+		// scalar lsTableHandleStatic_
+		ret.lsTableHandleStatic_ = static_cast<int32_t>(extractUint32(m));
+		m += 4;
+
+		// struct staggeredWriteResult_
+		size_t staggeredWriteResultSize = extractUint32(m);
+		ret.staggeredWriteResult_ =
+			IPADataSerializer<RPiStaggeredWritePayload>::deserialize(m + 4, m + 4 + staggeredWriteResultSize, cs);
+		m += 4 + staggeredWriteResultSize;
+
+		// ControlList controls_
+		size_t controlsSize = extractUint32(m);
+		if (controlsSize > 0)
+			ret.controls_ =
+				IPADataSerializer<ControlList>::deserialize(m + 4, m + 4 + controlsSize, cs);
+		m += 4 + controlsSize;
+
+		// scalar bufferFd_
+		ret.bufferFd_ = static_cast<int32_t>(extractUint32(m));
+
+		return ret;
+	}
+
+	static RPiConfigurePayload deserialize(std::vector<uint8_t>::iterator data_it1,
+					       std::vector<uint8_t>::iterator data_it2,
+					       std::vector<int32_t>::iterator fds_it1,
+					       std::vector<int32_t>::iterator fds_it2,
+					       ControlSerializer *cs)
+	{
+		std::vector<uint8_t> data(data_it1, data_it2);
+		std::vector<int32_t> fds(fds_it1, fds_it2);
+		return IPADataSerializer<RPiConfigurePayload>::deserialize(data, fds, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<RPiConfigureParams>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiConfigureParams data,
+		  ControlSerializer *cs)
+	{
+		std::vector<uint8_t> ret_data;
+		std::vector<int32_t> ret_fds;
+
+		// vector payload_
+		// only member, so don't need size
+		std::tie(ret_data, ret_fds) =
+			IPADataSerializer<std::vector<RPiConfigurePayload>>::serialize(data.payload_, cs);
+
+		return {ret_data, ret_fds};
+	}
+
+	static RPiConfigureParams deserialize(std::vector<uint8_t> &data,
+					      std::vector<int32_t> &fds,
+					      ControlSerializer *cs)
+	{
+		RPiConfigureParams ret;
+
+		// vector payload_
+		ret.payload_ =
+			IPADataSerializer<std::vector<RPiConfigurePayload>>::deserialize(data, fds, cs);
+
+		return ret;
+	}
+
+	static RPiConfigureParams deserialize(std::vector<uint8_t>::iterator data_it1,
+					      std::vector<uint8_t>::iterator data_it2,
+					      std::vector<int32_t>::iterator fds_it1,
+					      std::vector<int32_t>::iterator fds_it2,
+					      ControlSerializer *cs)
+	{
+		std::vector<uint8_t> data(data_it1, data_it2);
+		std::vector<int32_t> fds(fds_it1, fds_it2);
+		return IPADataSerializer<RPiConfigureParams>::deserialize(data, fds, cs);
+	}
+};
+
+template<>
+class IPADataSerializer<RPiEventParams>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiEventParams data,
+		  ControlSerializer *cs)
+	{
+		std::vector<uint8_t> ret_data;
+
+		// scalar ev_
+		appendUint32(ret_data, data.ev_);
+
+		// scalar bufferId_
+		appendUint32(ret_data, data.bufferId_);
+
+		// struct ispPrepare_
+		std::vector<uint8_t> ispPrepare;
+		std::tie(ispPrepare, std::ignore) =
+			IPADataSerializer<RPiIspPreparePayload>::serialize(data.ispPrepare_, cs);
+		appendUint32(ret_data, ispPrepare.size());
+		ret_data.insert(ret_data.end(), ispPrepare.begin(), ispPrepare.end());
+
+		// ControlList controls_
+		if (data.controls_.size() > 0) {
+			std::vector<uint8_t> controls;
+			std::tie(controls, std::ignore) =
+				IPADataSerializer<ControlList>::serialize(data.controls_,
+						data.controls_.infoMap() ? *data.controls_.infoMap() : RPiControls,
+						cs);
+			appendUint32(ret_data, controls.size());
+			ret_data.insert(ret_data.end(), controls.begin(), controls.end());
+		} else {
+			appendUint32(ret_data, 0);
+		}
+
+		// scalar bufferFd_
+		appendUint32(ret_data, static_cast<uint32_t>(data.bufferFd_));
+
+		return {ret_data, {}};
+	}
+
+	static RPiEventParams deserialize(std::vector<uint8_t> &data,
+					  ControlSerializer *cs)
+	{
+		RPiEventParams ret;
+		std::vector<uint8_t>::iterator m = data.begin();
+
+		// scalar ev_
+		ret.ev_ = static_cast<RPiEvents>(extractUint32(m));
+		m += 4;
+
+		// scalar bufferId_
+		ret.bufferId_ = extractUint32(m);
+		m += 4;
+
+		// struct ispPrepare_
+		size_t ispPrepareSize = extractUint32(m);
+		ret.ispPrepare_ =
+			IPADataSerializer<RPiIspPreparePayload>::deserialize(m + 4, m + 4 + ispPrepareSize, cs);
+		m += 4 + ispPrepareSize;
+
+		// ControlList controls_
+		size_t controlsSize = extractUint32(m);
+		if (controlsSize > 0)
+			ret.controls_ =
+				IPADataSerializer<ControlList>::deserialize(m + 4, m + 4 + controlsSize, cs);
+		m += 4 + controlsSize;
+
+		// scalar bufferFd_
+		ret.bufferFd_ = static_cast<int32_t>(extractUint32(m));
+
+		return ret;
+	}
+
+	static RPiEventParams deserialize(std::vector<uint8_t>::iterator it1,
+					  std::vector<uint8_t>::iterator it2,
+					  ControlSerializer *cs)
+	{
+		std::vector<uint8_t> data(it1, it2);
+		return IPADataSerializer<RPiEventParams>::deserialize(data, cs);
+	}
+
+};
+
+template<>
+class IPADataSerializer<RPiActionParams>
+{
+public:
+	static std::tuple<std::vector<uint8_t>, std::vector<int32_t>>
+	serialize(const RPiActionParams data,
+		  ControlSerializer *cs)
+	{
+		std::vector<uint8_t> ret_data;
+
+		// scalar op_
+		appendUint32(ret_data, data.op_);
+
+		// scalar bufferId_
+		appendUint32(ret_data, data.bufferId_);
+
+		// struct statsComplete_
+		std::vector<uint8_t> statsComplete;
+		std::tie(statsComplete, std::ignore) =
+			IPADataSerializer<RPiStatsCompletePayload>::serialize(data.statsComplete_, cs);
+		appendUint32(ret_data, statsComplete.size());
+		ret_data.insert(ret_data.end(), statsComplete.begin(), statsComplete.end());
+
+		// ControlList controls_
+		if (data.controls_.size() > 0) {
+			std::vector<uint8_t> controls;
+			std::tie(controls, std::ignore) =
+				IPADataSerializer<ControlList>::serialize(data.controls_,
+						data.controls_.infoMap() ? *data.controls_.infoMap() : RPiControls,
+						cs);
+			appendUint32(ret_data, controls.size());
+			ret_data.insert(ret_data.end(), controls.begin(), controls.end());
+		} else {
+			appendUint32(ret_data, 0);
+		}
+
+		return {ret_data, {}};
+	}
+
+	static RPiActionParams deserialize(std::vector<uint8_t> &data,
+					   ControlSerializer *cs)
+	{
+		RPiActionParams ret;
+		std::vector<uint8_t>::iterator m = data.begin();
+
+		// scalar op_
+		ret.op_ = static_cast<RPiActions>(extractUint32(m));
+		m += 4;
+
+		// scalar bufferId_
+		ret.bufferId_ = extractUint32(m);
+		m += 4;
+
+		// struct statsComplete_
+		size_t statsCompleteSize = extractUint32(m);
+		ret.statsComplete_ =
+			IPADataSerializer<RPiStatsCompletePayload>::deserialize(m + 4, m + 4 + statsCompleteSize, cs);
+		m += 4 + statsCompleteSize;
+
+		// ControlList controls_
+		size_t controlsSize = extractUint32(m);
+		if (controlsSize > 0)
+			ret.controls_ =
+				IPADataSerializer<ControlList>::deserialize(m + 4, m + 4 + controlsSize, cs);
+
+		return ret;
+	}
+
+	static RPiActionParams deserialize(std::vector<uint8_t>::iterator it1,
+					   std::vector<uint8_t>::iterator it2,
+					   ControlSerializer *cs)
+	{
+		std::vector<uint8_t> data(it1, it2);
+		return IPADataSerializer<RPiActionParams>::deserialize(data, cs);
+	}
+
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_INTERNAL_IPA_DATA_SERIALIZER_RASPBERRYPI_H__ */
diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
new file mode 100644
index 00000000..86332abc
--- /dev/null
+++ b/src/libcamera/ipa_data_serializer.cpp
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_data_serializer.cpp - Image Processing Algorithm data serializer
+ */
+
+#include "libcamera/internal/ipa_data_serializer.h"
+
+#include "libcamera/internal/log.h"
+
+/**
+ * \file ipa_ipa_data_serializer.h
+ * \brief IPA Data Serializer
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPADataSerializer)
+
+/**
+ * \class IPADataSerializer
+ * \brief IPA Data Serializer
+ *
+ */
+
+// TODO the rest of the documentation
+
+} /* namespace libcamera */
