diff mbox series

[3/3] media: Documentation: Add documentation for media jobs

Message ID 20250519140403.443915-4-dan.scally@ideasonboard.com
State New
Headers show
Series [1/3] media: mc: entity: Add pipeline_started/stopped ops | expand

Commit Message

Daniel Scally May 19, 2025, 2:04 p.m. UTC
Add a segment to mc-core.rst that explains the purpose behind the
media jobs framework and how to use it.

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
 Documentation/driver-api/media/mc-core.rst | 154 +++++++++++++++++++++
 1 file changed, 154 insertions(+)
diff mbox series

Patch

diff --git a/Documentation/driver-api/media/mc-core.rst b/Documentation/driver-api/media/mc-core.rst
index 1d010bd7ec49..53f13f857c1f 100644
--- a/Documentation/driver-api/media/mc-core.rst
+++ b/Documentation/driver-api/media/mc-core.rst
@@ -327,6 +327,158 @@  Call :c:func:`media_device_register()`, if media devnode isn't registered
 Call :c:func:`media_device_delete()` to free the media_device. Freeing is
 handled by the kref put handler.
 
+Media Jobs Framework
+^^^^^^^^^^^^^^^^^^^^
+
+The media jobs framework exists to facilitate situations in which multiple
+drivers must work together to properly operate a media pipeline in a driver
+agnostic way. The archetypical example is of a memory to memory ISP that does
+not include its own DMA input engine, and which must interact with the driver
+for one that has been integrated. Because the DMA engine and its driver may be
+different between each implementation, hardcoding calls of functions exported by
+the DMA engine driver would not be appropriate. The media jobs framework allows
+the drivers to define the steps that each must execute to correctly push data
+through the pipeline and then schedule the sequence of steps to run in a work
+queue.
+
+To start with each driver must acquire a reference to a
+:c:type:`media_jobs_scheduler` by calling :c:func:`media_jobs_get_scheduler()`,
+passing the pointer to their :c:type:`media_device`. This ensures that all of
+the drivers are working with the same scheduler. Drivers must then call
+:c:func:`media_jobs_add_job_setup_func()` to register a function that populates
+each job with the dependencies that must be cleared to allow it to operate, and
+the steps that must be carried out to execute it. For example:
+
+.. code-block:: c
+
+   static void isp_driver_run_step(void *data)
+   {
+       struct isp *isp = data;
+
+       /*
+        * Logic here to actually execute the necessary steps, for example we
+        * might configure some hardware registers.
+        */
+       ...;
+   }
+
+   static struct media_job_dep_ops ops = {
+       ...,
+   };
+
+   static int isp_driver_add_job_setup_func(struct media_job *job, void *data)
+   {
+       int ret;
+
+       ret = media_jobs_add_job_dep(job, &ops, data);
+       if (ret)
+           return ret;
+
+       ret = media_jobs_add_job_step(job, isp_driver_run_step, data,
+                                     MEDIA_JOBS_FL_STEP_ANYWHERE, 0);
+       if (ret)
+           return ret;
+
+       return 0;
+   }
+
+The flags parameter of `media_jobs_add_job_step()` must be one of
+:c:macro:`MEDIA_JOBS_FL_STEP_ANYWHERE`, :c:macro:`MEDIA_JOBS_FL_STEP_FROM_FRONT`
+or :c:macro:`MEDIA_JOBS_FL_STEP_FROM_BACK`. The flag and pos parameters together
+define the order of the step within the job. Steps added with
+`MEDIA_JOBS_FL_STEP_ANYWHERE` will go after all steps that are added with
+`MEDIA_JOBS_FL_STEP_FROM_FRONT` and all steps with `MEDIA_JOBS_FL_STEP_ANYWHERE`
+that either have a lower `pos` or were previously added. They will go before all
+those added with `MEDIA_JOBS_FL_STEP_FROM_BACK` and all steps with
+`MEDIA_JOBS_FL_STEP_ANYWHERE` that have a higher `pos`. Steps added with
+`MEDIA_JOBS_FL_STEP_FROM_FRONT` will go `pos` places from the front of the list,
+and steps added with `flags` set to `MEDIA_JOBS_FL_STEP_FROM_BACK`` will go
+`pos` places from the end of the list. This allows multiple drivers to quite
+precisely define which steps need to be executed and what order they should be
+executed in.
+
+Adding a step with the same `flags` and `pos` as a previously added step will
+result in an error.
+
+The functions held in :c:type:`media_job_dep_ops` define how the media jobs
+framework handles job dependencies. It is expected that there will be some hard
+dependencies before a job can be executed; for example pushing a buffer of image
+data through an ISP pipeline necessarily requires that an input buffer be ready
+and an output buffer be ready to accept the processed data. The operations ask
+the driver if the dependencies are met, tell the driver that a job has been
+queued and reset the dependencies in the event the job is cancelled:
+
+.. code-block:: c
+
+   struct isp {
+
+       ...;
+
+       struct {
+           struct list_head pending;
+	   struct list_head processing;
+       } buffers;
+   }
+
+   static bool isp_driver_check_dep(void *data)
+   {
+       struct isp *isp = data;
+
+       /*
+        * Do we have a buffer queued ready to accept the ISP's output data?
+        */
+       if (list_empty(isp->buffers.pending))
+           return false;
+
+       return true;
+   }
+
+   static void isp_driver_clear_dep(void *data)
+   {
+       struct isp *isp = data;
+       struct buf *buf;
+
+       /*
+        * We need to "consume" the buffer so that it's not also considered as
+        * meeting this dependency for the next attempt to queue a job
+        */
+       buf = list_first_entry(&isp->buffers.pending, struct buf, list);
+       list_move_tail(&buf->list, isp->buffers.processing);
+   }
+
+   static void isp_driver_reset_dep(void *data)
+   {
+       struct isp *isp = data;
+       struct buf *buf;
+
+       /*
+        * If a queued job is cancelled then we need to return the dependency to
+        * its original state, which in this example means returning it to the
+        * pending queue.
+        */
+       buf = list_first_entry(&isp->buffers.pending, struct buf, list);
+       list_move_tail(&buf->list, isp->buffers.pending);
+   }
+
+   static struct media_job_dep_ops ops = {
+       .check_dep = isp_driver_check_dep,
+       .clear_dep = isp_driver_clear_dep,
+       .reset_dep = isp_driver_reset_dep,
+   };
+
+The actual creation and queueing of the jobs should be done by the drivers by
+calling :c:func:`media_jobs_try_queue_job()` at any time a dependency of the
+job is met - for example (following the earlier example) when a buffer is queued
+to either the ISP or DMA engine's driver. When all of the dependencies that are
+necessary for a job to be queued are met, this function will push a job to the
+scheduler's queue.
+
+The scheduler has a workqueue that runs the jobs. This is triggered by calls to
+the :c:func:`media_jobs_run_jobs()` function, which must be called periodically
+as the pipeline is running. When the streaming is finished the drivers should
+shut down the workqueue and cancel the queued jobs by calling
+:c:func:`media_jobs_cancel_jobs()`.
+
 API Definitions
 ^^^^^^^^^^^^^^^
 
@@ -336,6 +488,8 @@  API Definitions
 
 .. kernel-doc:: include/media/media-entity.h
 
+.. kernel-doc:: include/media/media-jobs.h
+
 .. kernel-doc:: include/media/media-request.h
 
 .. kernel-doc:: include/media/media-dev-allocator.h