[3/3] libcamera: v4l2_subdevice: Update to the new kernel routing API
diff mbox series

Message ID 20240601220320.6484-4-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • libcamera: Update to the new upstream subdev routing API
Related show

Commit Message

Laurent Pinchart June 1, 2024, 10:03 p.m. UTC
The subdev embedded data support series includes a change to the
VIDIOC_SUBDEV_G_ROUTING and VIDIOC_SUBDEV_S_ROUTING ioctls that impacts
the userspace API.

Update to the new API, while preserving backward compatibility to ease
the transition. Document the backward compatibility to only be supported
for two kernel releases. As the routing API isn't enabled in any
upstream kernel yet, users of the API need kernel patches, and are
expected to be able to upgrade quickly.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since combined RFC:

- Drop -ENOTTY special handling in getRouting()
- Preserve backward compatibility for a couple of kernel releases.
---
 include/libcamera/internal/v4l2_subdevice.h |   3 +
 src/libcamera/v4l2_subdevice.cpp            | 121 +++++++++++++++++++-
 2 files changed, 120 insertions(+), 4 deletions(-)

Comments

Kieran Bingham June 3, 2024, 9:05 a.m. UTC | #1
Quoting Laurent Pinchart (2024-06-01 23:03:20)
> The subdev embedded data support series includes a change to the
> VIDIOC_SUBDEV_G_ROUTING and VIDIOC_SUBDEV_S_ROUTING ioctls that impacts
> the userspace API.
> 
> Update to the new API, while preserving backward compatibility to ease
> the transition. Document the backward compatibility to only be supported
> for two kernel releases. As the routing API isn't enabled in any
> upstream kernel yet, users of the API need kernel patches, and are
> expected to be able to upgrade quickly.

That sounds reasonable to me.

> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> Changes since combined RFC:
> 
> - Drop -ENOTTY special handling in getRouting()
> - Preserve backward compatibility for a couple of kernel releases.
> ---
>  include/libcamera/internal/v4l2_subdevice.h |   3 +
>  src/libcamera/v4l2_subdevice.cpp            | 121 +++++++++++++++++++-
>  2 files changed, 120 insertions(+), 4 deletions(-)
> 
> diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h
> index a1de0fb00ee3..194382f84d97 100644
> --- a/include/libcamera/internal/v4l2_subdevice.h
> +++ b/include/libcamera/internal/v4l2_subdevice.h
> @@ -176,6 +176,9 @@ private:
>         std::vector<SizeRange> enumPadSizes(const Stream &stream,
>                                             unsigned int code);
>  
> +       int getRoutingLegacy(Routing *routing, Whence whence);
> +       int setRoutingLegacy(Routing *routing, Whence whence);
> +
>         const MediaEntity *entity_;
>  
>         std::string model_;
> diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> index 6da77775778f..e7be21d7250e 100644
> --- a/src/libcamera/v4l2_subdevice.cpp
> +++ b/src/libcamera/v4l2_subdevice.cpp
> @@ -1366,8 +1366,62 @@ void routeToKernel(const V4L2Subdevice::Route &route,
>         kroute.flags = route.flags;
>  }
>  
> +/*
> + * Legacy routing support for pre-v6.10-rc1 kernels. Drop when v6.12-rc1 gets
> + * released.
> + */
> +struct v4l2_subdev_routing_legacy {
> +       __u32 which;
> +       __u32 num_routes;
> +       __u64 routes;
> +       __u32 reserved[6];
> +};
> +
> +#define VIDIOC_SUBDEV_G_ROUTING_LEGACY _IOWR('V', 38, struct v4l2_subdev_routing_legacy)
> +#define VIDIOC_SUBDEV_S_ROUTING_LEGACY _IOWR('V', 39, struct v4l2_subdev_routing_legacy)
> +
>  } /* namespace */
>  
> +int V4L2Subdevice::getRoutingLegacy(Routing *routing, Whence whence)
> +{
> +       struct v4l2_subdev_routing_legacy rt = {};
> +
> +       rt.which = whence;
> +
> +       int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
> +       if (ret == 0 || ret == -ENOTTY)
> +               return ret;
> +
> +       if (ret != -ENOSPC) {
> +               LOG(V4L2, Error)
> +                       << "Failed to retrieve number of routes: "
> +                       << strerror(-ret);
> +               return ret;
> +       }
> +
> +       std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
> +       rt.routes = reinterpret_cast<uintptr_t>(routes.data());
> +
> +       ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
> +       if (ret) {
> +               LOG(V4L2, Error)
> +                       << "Failed to retrieve routes: " << strerror(-ret);
> +               return ret;
> +       }
> +
> +       if (rt.num_routes != routes.size()) {
> +               LOG(V4L2, Error) << "Invalid number of routes";
> +               return -EINVAL;
> +       }
> +
> +       routing->resize(rt.num_routes);
> +
> +       for (const auto &[i, route] : utils::enumerate(routes))
> +               routeFromKernel((*routing)[i], route);
> +
> +       return 0;
> +}
> +
>  /**
>   * \brief Retrieve the subdevice's internal routing table
>   * \param[out] routing The routing table
> @@ -1388,19 +1442,25 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
>         rt.which = whence;
>  
>         int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
> -       if (ret == 0 || ret == -ENOTTY)
> -               return ret;
> +       if (ret == -ENOTTY)
> +               return V4L2Subdevice::getRoutingLegacy(routing, whence);
>  
> -       if (ret != -ENOSPC) {
> +       if (ret) {
>                 LOG(V4L2, Error)
>                         << "Failed to retrieve number of routes: "
>                         << strerror(-ret);
>                 return ret;
>         }
>  
> +       if (!rt.num_routes)
> +               return 0;
> +
>         std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
>         rt.routes = reinterpret_cast<uintptr_t>(routes.data());
>  
> +       rt.len_routes = rt.num_routes;
> +       rt.num_routes = 0;
> +
>         ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
>         if (ret) {
>                 LOG(V4L2, Error)
> @@ -1421,6 +1481,33 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
>         return 0;
>  }
>  
> +int V4L2Subdevice::setRoutingLegacy(Routing *routing, Whence whence)
> +{
> +       std::vector<struct v4l2_subdev_route> routes{ routing->size() };
> +
> +       for (const auto &[i, route] : utils::enumerate(*routing))
> +               routeToKernel(route, routes[i]);
> +
> +       struct v4l2_subdev_routing_legacy rt = {};
> +       rt.which = whence;
> +       rt.num_routes = routes.size();
> +       rt.routes = reinterpret_cast<uintptr_t>(routes.data());
> +
> +       int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING_LEGACY, &rt);
> +       if (ret) {
> +               LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
> +               return ret;
> +       }
> +
> +       routes.resize(rt.num_routes);

Can the 'kernel may return a larger routing table than was set' issue
happen here in the legacy version?

Perhaps it was only implemented later (hence the legacy) but we do still
have the rt.num_routes to check ... ?



> +       routing->resize(rt.num_routes);
> +
> +       for (const auto &[i, route] : utils::enumerate(routes))
> +               routeFromKernel((*routing)[i], route);
> +
> +       return 0;
> +}
> +
>  /**
>   * \brief Set a routing table on the V4L2 subdevice
>   * \param[inout] routing The routing table
> @@ -1447,16 +1534,42 @@ int V4L2Subdevice::setRouting(Routing *routing, Whence whence)
>  
>         struct v4l2_subdev_routing rt = {};
>         rt.which = whence;
> +       rt.len_routes = routes.size();
>         rt.num_routes = routes.size();
>         rt.routes = reinterpret_cast<uintptr_t>(routes.data());
>  
>         int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING, &rt);
> +       if (ret == -ENOTTY)
> +               return setRoutingLegacy(routing, whence);
> +
>         if (ret) {
>                 LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
>                 return ret;
>         }
>  
> -       routes.resize(rt.num_routes);
> +       /*
> +        * The kernel wants to return more routes than we have space for. We
> +        * need to issue a VIDIOC_SUBDEV_G_ROUTING call.
> +        */

It's a real nit - but the way this is phrased and being outside of the
conditional - makes it sound like /the kernel always/ wants to do this.

Phrased like that I'd put the comment /inside/ the conditional.
Outside I would phrase it as

	* The kernel may want to return more routes than we have space
	* for. In that event, we must issue a VIDIOC_SUBDEV_G_ROUTING
	* call to determine the correct size.

But it doesn't specifically matter here. It's just the context of the
position of the comment...

> +       if (rt.num_routes > routes.size()) {
> +               routes.resize(rt.num_routes);
> +
> +               rt.len_routes = rt.num_routes;
> +               rt.num_routes = 0;
> +
> +               ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
> +               if (ret) {
> +                       LOG(V4L2, Error)
> +                               << "Failed to retrieve routes: " << strerror(-ret);
> +                       return ret;

I presume the 'set routing' has succeeded even though not all routes
were specified, and this is simply returning the 'entire' routing table
back and the extra get is to know that we have all the context in
userspace for the next part...

I'm happy here. I won't consider the return routing size on the legacy a
blocker as that's current version and ... legacy to be removed, so I'll
leave it to you to determine.


Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

> +               }
> +       }
> +
> +       if (rt.num_routes != routes.size()) {
> +               LOG(V4L2, Error) << "Invalid number of routes";
> +               return -EINVAL;
> +       }
> +
>         routing->resize(rt.num_routes);
>  
>         for (const auto &[i, route] : utils::enumerate(routes))
> -- 
> Regards,
> 
> Laurent Pinchart
>
Laurent Pinchart June 3, 2024, 9:18 a.m. UTC | #2
Hi Kieran,

On Mon, Jun 03, 2024 at 10:05:59AM +0100, Kieran Bingham wrote:
> Quoting Laurent Pinchart (2024-06-01 23:03:20)
> > The subdev embedded data support series includes a change to the
> > VIDIOC_SUBDEV_G_ROUTING and VIDIOC_SUBDEV_S_ROUTING ioctls that impacts
> > the userspace API.
> > 
> > Update to the new API, while preserving backward compatibility to ease
> > the transition. Document the backward compatibility to only be supported
> > for two kernel releases. As the routing API isn't enabled in any
> > upstream kernel yet, users of the API need kernel patches, and are
> > expected to be able to upgrade quickly.
> 
> That sounds reasonable to me.
> 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> > Changes since combined RFC:
> > 
> > - Drop -ENOTTY special handling in getRouting()
> > - Preserve backward compatibility for a couple of kernel releases.
> > ---
> >  include/libcamera/internal/v4l2_subdevice.h |   3 +
> >  src/libcamera/v4l2_subdevice.cpp            | 121 +++++++++++++++++++-
> >  2 files changed, 120 insertions(+), 4 deletions(-)
> > 
> > diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h
> > index a1de0fb00ee3..194382f84d97 100644
> > --- a/include/libcamera/internal/v4l2_subdevice.h
> > +++ b/include/libcamera/internal/v4l2_subdevice.h
> > @@ -176,6 +176,9 @@ private:
> >         std::vector<SizeRange> enumPadSizes(const Stream &stream,
> >                                             unsigned int code);
> >  
> > +       int getRoutingLegacy(Routing *routing, Whence whence);
> > +       int setRoutingLegacy(Routing *routing, Whence whence);
> > +
> >         const MediaEntity *entity_;
> >  
> >         std::string model_;
> > diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
> > index 6da77775778f..e7be21d7250e 100644
> > --- a/src/libcamera/v4l2_subdevice.cpp
> > +++ b/src/libcamera/v4l2_subdevice.cpp
> > @@ -1366,8 +1366,62 @@ void routeToKernel(const V4L2Subdevice::Route &route,
> >         kroute.flags = route.flags;
> >  }
> >  
> > +/*
> > + * Legacy routing support for pre-v6.10-rc1 kernels. Drop when v6.12-rc1 gets
> > + * released.
> > + */
> > +struct v4l2_subdev_routing_legacy {
> > +       __u32 which;
> > +       __u32 num_routes;
> > +       __u64 routes;
> > +       __u32 reserved[6];
> > +};
> > +
> > +#define VIDIOC_SUBDEV_G_ROUTING_LEGACY _IOWR('V', 38, struct v4l2_subdev_routing_legacy)
> > +#define VIDIOC_SUBDEV_S_ROUTING_LEGACY _IOWR('V', 39, struct v4l2_subdev_routing_legacy)
> > +
> >  } /* namespace */
> >  
> > +int V4L2Subdevice::getRoutingLegacy(Routing *routing, Whence whence)
> > +{
> > +       struct v4l2_subdev_routing_legacy rt = {};
> > +
> > +       rt.which = whence;
> > +
> > +       int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
> > +       if (ret == 0 || ret == -ENOTTY)
> > +               return ret;
> > +
> > +       if (ret != -ENOSPC) {
> > +               LOG(V4L2, Error)
> > +                       << "Failed to retrieve number of routes: "
> > +                       << strerror(-ret);
> > +               return ret;
> > +       }
> > +
> > +       std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
> > +       rt.routes = reinterpret_cast<uintptr_t>(routes.data());
> > +
> > +       ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
> > +       if (ret) {
> > +               LOG(V4L2, Error)
> > +                       << "Failed to retrieve routes: " << strerror(-ret);
> > +               return ret;
> > +       }
> > +
> > +       if (rt.num_routes != routes.size()) {
> > +               LOG(V4L2, Error) << "Invalid number of routes";
> > +               return -EINVAL;
> > +       }
> > +
> > +       routing->resize(rt.num_routes);
> > +
> > +       for (const auto &[i, route] : utils::enumerate(routes))
> > +               routeFromKernel((*routing)[i], route);
> > +
> > +       return 0;
> > +}
> > +
> >  /**
> >   * \brief Retrieve the subdevice's internal routing table
> >   * \param[out] routing The routing table
> > @@ -1388,19 +1442,25 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
> >         rt.which = whence;
> >  
> >         int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
> > -       if (ret == 0 || ret == -ENOTTY)
> > -               return ret;
> > +       if (ret == -ENOTTY)
> > +               return V4L2Subdevice::getRoutingLegacy(routing, whence);
> >  
> > -       if (ret != -ENOSPC) {
> > +       if (ret) {
> >                 LOG(V4L2, Error)
> >                         << "Failed to retrieve number of routes: "
> >                         << strerror(-ret);
> >                 return ret;
> >         }
> >  
> > +       if (!rt.num_routes)
> > +               return 0;
> > +
> >         std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
> >         rt.routes = reinterpret_cast<uintptr_t>(routes.data());
> >  
> > +       rt.len_routes = rt.num_routes;
> > +       rt.num_routes = 0;
> > +
> >         ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
> >         if (ret) {
> >                 LOG(V4L2, Error)
> > @@ -1421,6 +1481,33 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
> >         return 0;
> >  }
> >  
> > +int V4L2Subdevice::setRoutingLegacy(Routing *routing, Whence whence)
> > +{
> > +       std::vector<struct v4l2_subdev_route> routes{ routing->size() };
> > +
> > +       for (const auto &[i, route] : utils::enumerate(*routing))
> > +               routeToKernel(route, routes[i]);
> > +
> > +       struct v4l2_subdev_routing_legacy rt = {};
> > +       rt.which = whence;
> > +       rt.num_routes = routes.size();
> > +       rt.routes = reinterpret_cast<uintptr_t>(routes.data());
> > +
> > +       int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING_LEGACY, &rt);
> > +       if (ret) {
> > +               LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
> > +               return ret;
> > +       }
> > +
> > +       routes.resize(rt.num_routes);
> 
> Can the 'kernel may return a larger routing table than was set' issue
> happen here in the legacy version?

No, only the new API allows this. The old API didn't update num_routes.

> Perhaps it was only implemented later (hence the legacy) but we do still
> have the rt.num_routes to check ... ?
> 
> > +       routing->resize(rt.num_routes);
> > +
> > +       for (const auto &[i, route] : utils::enumerate(routes))
> > +               routeFromKernel((*routing)[i], route);
> > +
> > +       return 0;
> > +}
> > +
> >  /**
> >   * \brief Set a routing table on the V4L2 subdevice
> >   * \param[inout] routing The routing table
> > @@ -1447,16 +1534,42 @@ int V4L2Subdevice::setRouting(Routing *routing, Whence whence)
> >  
> >         struct v4l2_subdev_routing rt = {};
> >         rt.which = whence;
> > +       rt.len_routes = routes.size();
> >         rt.num_routes = routes.size();
> >         rt.routes = reinterpret_cast<uintptr_t>(routes.data());
> >  
> >         int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING, &rt);
> > +       if (ret == -ENOTTY)
> > +               return setRoutingLegacy(routing, whence);
> > +
> >         if (ret) {
> >                 LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
> >                 return ret;
> >         }
> >  
> > -       routes.resize(rt.num_routes);
> > +       /*
> > +        * The kernel wants to return more routes than we have space for. We
> > +        * need to issue a VIDIOC_SUBDEV_G_ROUTING call.
> > +        */
> 
> It's a real nit - but the way this is phrased and being outside of the
> conditional - makes it sound like /the kernel always/ wants to do this.
> 
> Phrased like that I'd put the comment /inside/ the conditional.
> Outside I would phrase it as
> 
> 	* The kernel may want to return more routes than we have space
> 	* for. In that event, we must issue a VIDIOC_SUBDEV_G_ROUTING
> 	* call to determine the correct size.

Not to determine the correct size (that information is returned by
S_ROUTING), but to retrieve the additional routes. I'll update the
comment to

        /*
         * The kernel may want to return more routes than we have space for. In
         * that event, we must issue a VIDIOC_SUBDEV_G_ROUTING call to retrieve
         * the additional routes.
         */

> But it doesn't specifically matter here. It's just the context of the
> position of the comment...
> 
> > +       if (rt.num_routes > routes.size()) {
> > +               routes.resize(rt.num_routes);
> > +
> > +               rt.len_routes = rt.num_routes;
> > +               rt.num_routes = 0;
> > +
> > +               ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
> > +               if (ret) {
> > +                       LOG(V4L2, Error)
> > +                               << "Failed to retrieve routes: " << strerror(-ret);
> > +                       return ret;
> 
> I presume the 'set routing' has succeeded even though not all routes
> were specified, and this is simply returning the 'entire' routing table
> back and the extra get is to know that we have all the context in
> userspace for the next part...

Yes.

> I'm happy here. I won't consider the return routing size on the legacy a
> blocker as that's current version and ... legacy to be removed, so I'll
> leave it to you to determine.
> 
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> > +               }
> > +       }
> > +
> > +       if (rt.num_routes != routes.size()) {
> > +               LOG(V4L2, Error) << "Invalid number of routes";
> > +               return -EINVAL;
> > +       }
> > +
> >         routing->resize(rt.num_routes);
> >  
> >         for (const auto &[i, route] : utils::enumerate(routes))

Patch
diff mbox series

diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h
index a1de0fb00ee3..194382f84d97 100644
--- a/include/libcamera/internal/v4l2_subdevice.h
+++ b/include/libcamera/internal/v4l2_subdevice.h
@@ -176,6 +176,9 @@  private:
 	std::vector<SizeRange> enumPadSizes(const Stream &stream,
 					    unsigned int code);
 
+	int getRoutingLegacy(Routing *routing, Whence whence);
+	int setRoutingLegacy(Routing *routing, Whence whence);
+
 	const MediaEntity *entity_;
 
 	std::string model_;
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index 6da77775778f..e7be21d7250e 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -1366,8 +1366,62 @@  void routeToKernel(const V4L2Subdevice::Route &route,
 	kroute.flags = route.flags;
 }
 
+/*
+ * Legacy routing support for pre-v6.10-rc1 kernels. Drop when v6.12-rc1 gets
+ * released.
+ */
+struct v4l2_subdev_routing_legacy {
+	__u32 which;
+	__u32 num_routes;
+	__u64 routes;
+	__u32 reserved[6];
+};
+
+#define VIDIOC_SUBDEV_G_ROUTING_LEGACY	_IOWR('V', 38, struct v4l2_subdev_routing_legacy)
+#define VIDIOC_SUBDEV_S_ROUTING_LEGACY	_IOWR('V', 39, struct v4l2_subdev_routing_legacy)
+
 } /* namespace */
 
+int V4L2Subdevice::getRoutingLegacy(Routing *routing, Whence whence)
+{
+	struct v4l2_subdev_routing_legacy rt = {};
+
+	rt.which = whence;
+
+	int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
+	if (ret == 0 || ret == -ENOTTY)
+		return ret;
+
+	if (ret != -ENOSPC) {
+		LOG(V4L2, Error)
+			<< "Failed to retrieve number of routes: "
+			<< strerror(-ret);
+		return ret;
+	}
+
+	std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
+	rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+
+	ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
+	if (ret) {
+		LOG(V4L2, Error)
+			<< "Failed to retrieve routes: " << strerror(-ret);
+		return ret;
+	}
+
+	if (rt.num_routes != routes.size()) {
+		LOG(V4L2, Error) << "Invalid number of routes";
+		return -EINVAL;
+	}
+
+	routing->resize(rt.num_routes);
+
+	for (const auto &[i, route] : utils::enumerate(routes))
+		routeFromKernel((*routing)[i], route);
+
+	return 0;
+}
+
 /**
  * \brief Retrieve the subdevice's internal routing table
  * \param[out] routing The routing table
@@ -1388,19 +1442,25 @@  int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
 	rt.which = whence;
 
 	int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
-	if (ret == 0 || ret == -ENOTTY)
-		return ret;
+	if (ret == -ENOTTY)
+		return V4L2Subdevice::getRoutingLegacy(routing, whence);
 
-	if (ret != -ENOSPC) {
+	if (ret) {
 		LOG(V4L2, Error)
 			<< "Failed to retrieve number of routes: "
 			<< strerror(-ret);
 		return ret;
 	}
 
+	if (!rt.num_routes)
+		return 0;
+
 	std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
 	rt.routes = reinterpret_cast<uintptr_t>(routes.data());
 
+	rt.len_routes = rt.num_routes;
+	rt.num_routes = 0;
+
 	ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
 	if (ret) {
 		LOG(V4L2, Error)
@@ -1421,6 +1481,33 @@  int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
 	return 0;
 }
 
+int V4L2Subdevice::setRoutingLegacy(Routing *routing, Whence whence)
+{
+	std::vector<struct v4l2_subdev_route> routes{ routing->size() };
+
+	for (const auto &[i, route] : utils::enumerate(*routing))
+		routeToKernel(route, routes[i]);
+
+	struct v4l2_subdev_routing_legacy rt = {};
+	rt.which = whence;
+	rt.num_routes = routes.size();
+	rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+
+	int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING_LEGACY, &rt);
+	if (ret) {
+		LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
+		return ret;
+	}
+
+	routes.resize(rt.num_routes);
+	routing->resize(rt.num_routes);
+
+	for (const auto &[i, route] : utils::enumerate(routes))
+		routeFromKernel((*routing)[i], route);
+
+	return 0;
+}
+
 /**
  * \brief Set a routing table on the V4L2 subdevice
  * \param[inout] routing The routing table
@@ -1447,16 +1534,42 @@  int V4L2Subdevice::setRouting(Routing *routing, Whence whence)
 
 	struct v4l2_subdev_routing rt = {};
 	rt.which = whence;
+	rt.len_routes = routes.size();
 	rt.num_routes = routes.size();
 	rt.routes = reinterpret_cast<uintptr_t>(routes.data());
 
 	int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING, &rt);
+	if (ret == -ENOTTY)
+		return setRoutingLegacy(routing, whence);
+
 	if (ret) {
 		LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
 		return ret;
 	}
 
-	routes.resize(rt.num_routes);
+	/*
+	 * The kernel wants to return more routes than we have space for. We
+	 * need to issue a VIDIOC_SUBDEV_G_ROUTING call.
+	 */
+	if (rt.num_routes > routes.size()) {
+		routes.resize(rt.num_routes);
+
+		rt.len_routes = rt.num_routes;
+		rt.num_routes = 0;
+
+		ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
+		if (ret) {
+			LOG(V4L2, Error)
+				<< "Failed to retrieve routes: " << strerror(-ret);
+			return ret;
+		}
+	}
+
+	if (rt.num_routes != routes.size()) {
+		LOG(V4L2, Error) << "Invalid number of routes";
+		return -EINVAL;
+	}
+
 	routing->resize(rt.num_routes);
 
 	for (const auto &[i, route] : utils::enumerate(routes))