[{"id":967,"web_url":"https://patchwork.libcamera.org/comment/967/","msgid":"<20190228213947.GJ7811@pendragon.ideasonboard.com>","date":"2019-02-28T21:39:47","subject":"Re: [libcamera-devel] [PATCH v3 3/3] libcamera: camera: add state\n\tmachine to control access from applications","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Niklas,\n\nThank you for the patch.\n\nOn Thu, Feb 28, 2019 at 07:51:26PM +0100, Niklas Söderlund wrote:\n> There is a need to better control the order of operations an application\n> performs on a camera for it to function correctly. Add a basic state\n> machine to ensure applications perform operations on the camera in good\n> order.\n> \n> Internal to the Camera states are added; Available, Acquired,\n> Configured, Prepared and Running. Each state represents a higher state\n> of configuration of the camera ultimately leading to the highest state\n> where the camera is capturing frames. Each state supports a subset of\n> operations the application may perform.\n> \n> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> ---\n>  include/libcamera/camera.h |  18 ++-\n>  src/libcamera/camera.cpp   | 266 +++++++++++++++++++++++++++++++------\n>  2 files changed, 238 insertions(+), 46 deletions(-)\n> \n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index 9c8ae01ed5c607f1..e5212cf05d221279 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -39,7 +39,7 @@ public:\n>  \tSignal<Camera *> disconnected;\n>  \n>  \tint acquire();\n> -\tvoid release();\n> +\tint release();\n>  \n>  \tconst std::set<Stream *> &streams() const;\n>  \tstd::map<Stream *, StreamConfiguration>\n> @@ -47,7 +47,7 @@ public:\n>  \tint configureStreams(std::map<Stream *, StreamConfiguration> &config);\n>  \n>  \tint allocateBuffers();\n> -\tvoid freeBuffers();\n> +\tint freeBuffers();\n>  \n>  \tRequest *createRequest();\n>  \tint queueRequest(Request *request);\n> @@ -56,20 +56,30 @@ public:\n>  \tint stop();\n>  \n>  private:\n> +\tenum State {\n> +\t\tCameraAvailable,\n> +\t\tCameraAcquired,\n> +\t\tCameraConfigured,\n> +\t\tCameraPrepared,\n> +\t\tCameraRunning,\n> +\t};\n> +\n>  \tCamera(PipelineHandler *pipe, const std::string &name);\n>  \t~Camera();\n>  \n> +\tbool stateBetween(State low, State high) const;\n> +\tbool stateIs(State state) const;\n> +\n>  \tfriend class PipelineHandler;\n>  \tvoid disconnect();\n> -\tint exclusiveAccess();\n>  \n>  \tstd::shared_ptr<PipelineHandler> pipe_;\n>  \tstd::string name_;\n>  \tstd::set<Stream *> streams_;\n>  \tstd::set<Stream *> activeStreams_;\n>  \n> -\tbool acquired_;\n>  \tbool disconnected_;\n> +\tState state_;\n>  };\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 84b97b5c2ce94ecf..0b06c0d838b356f3 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -11,6 +11,7 @@\n>  \n>  #include \"log.h\"\n>  #include \"pipeline_handler.h\"\n> +#include \"utils.h\"\n>  \n>  /**\n>   * \\file camera.h\n> @@ -42,6 +43,9 @@ LOG_DECLARE_CATEGORY(Camera)\n>   * \\class Camera\n>   * \\brief Camera device\n>   *\n> + * \\todo Add documentation for camera start timings. What exactly does the\n> + * camera expect the pipeline handler to do when start() is called?\n> + *\n>   * The Camera class models a camera capable of producing one or more image\n>   * streams from a single image source. It provides the main interface to\n>   * configuring and controlling the device, and capturing image streams. It is\n> @@ -52,6 +56,78 @@ LOG_DECLARE_CATEGORY(Camera)\n>   * created with the create() function which returns a shared pointer. The\n>   * Camera constructors and destructor are private, to prevent instances from\n>   * being constructed and destroyed manually.\n> + *\n> + * \\section camera_operation Operating the Camera\n> + *\n> + * An application needs to perform a sequence of operations on a camera before\n> + * it is ready to process requests. The camera needs to be acquired, configured\n> + * and resources allocated or imported to prepare the camera for capture. Once\n> + * started the camera can process requests until it is stopped. When an\n> + * application is done with a camera all resources allocated needs to be freed\n\ns/needs/need/\n\n> + * and the camera released.\n> + *\n> + * An application may start and stop a camera multiple times as long as it is\n> + * not released. The camera may also be reconfigured provided that all\n> + * resources allocated are freed prior to the reconfiguration.\n> + *\n> + * \\subsection Camera States\n> + *\n> + * To help manage the sequence of operations needed to control the camera a set\n> + * of states are defined. Each state describes which operations may be performed\n> + * on the camera. Operations not listed in the state diagram are allowed in all\n> + * states.\n> + *\n> + * \\dot\n> + * digraph camera_state_machine {\n> + *   node [shape = doublecircle ]; Available;\n> + *   node [shape = circle ]; Acquired;\n> + *   node [shape = circle ]; Configured;\n> + *   node [shape = circle ]; Prepared;\n> + *   node [shape = circle ]; Running;\n> + *\n> + *   Available -> Available [label = \"release()\"];\n> + *   Available -> Acquired [label = \"acquire()\"];\n> + *\n> + *   Acquired -> Available [label = \"release()\"];\n> + *   Acquired -> Configured [label = \"configureStreams()\"];\n> + *\n> + *   Configured -> Available [label = \"release()\"];\n> + *   Configured -> Configured [label = \"configureStreams()\"];\n> + *   Configured -> Prepared [label = \"allocateBuffers()\"];\n> + *\n> + *   Prepared -> Configured [label = \"freeBuffers()\"];\n> + *   Prepared -> Prepared [label = \"createRequest()\"];\n> + *   Prepared -> Running [label = \"start()\"];\n> + *\n> + *   Running -> Prepared [label = \"stop()\"];\n> + *   Running -> Running [label = \"createRequest(), queueRequest()\"];\n> + * }\n> + * \\enddot\n> + *\n> + * \\subsubsection Available\n> + * The base state of a camera, an application can inspect the properties of the\n> + * camera to determine if it wishes to use it. If an application wishes to use\n> + * a camera it should acquire() it to proceed to the Acquired state.\n> + *\n> + * \\subsubsection Acquired\n> + * In the acquired state an application has exclusive access to the camera and\n> + * may modify the camera's parameters to configure it and proceed to the\n> + * Configured state.\n> + *\n> + * \\subsubsection Configured\n> + * The camera is configured and ready for the application to prepare it with\n> + * resources. The camera may be reconfigured multiple times until resources\n> + * are provided and the state progresses to Prepared.\n> + *\n> + * \\subsubsection Prepared\n> + * The camera has been configured and provided with resources and is ready to be\n> + * started. The application may free the camera's resources to get back to the\n> + * Configured state or start it to progress to the Running state.\n\ns/start/start()/\n\n> + *\n> + * \\subsubsection Running\n> + * The camera is running and ready to process requests queued by the\n> + * application. The camera remains in this state until it is stopped and moved\n> + * to the Prepared state.\n>   */\n>  \n>  /**\n> @@ -116,17 +192,55 @@ const std::string &Camera::name() const\n>   */\n>  \n>  Camera::Camera(PipelineHandler *pipe, const std::string &name)\n> -\t: pipe_(pipe->shared_from_this()), name_(name), acquired_(false),\n> -\t  disconnected_(false)\n> +\t: pipe_(pipe->shared_from_this()), name_(name), disconnected_(false),\n> +\t  state_(CameraAvailable)\n>  {\n>  }\n>  \n>  Camera::~Camera()\n>  {\n> -\tif (acquired_)\n> +\tif (!stateIs(CameraAvailable))\n>  \t\tLOG(Camera, Error) << \"Removing camera while still in use\";\n>  }\n>  \n> +static const char *const camera_state_name[] = {\n\nMaybe camera_state_names ?\n\n> +\t\"Available\",\n> +\t\"Acquired\",\n> +\t\"Configured\",\n> +\t\"Prepared\",\n> +\t\"Running\",\n> +};\n> +\n> +bool Camera::stateBetween(State low, State high) const\n> +{\n> +\tif (state_ >= low && state_ <= high)\n> +\t\treturn true;\n> +\n> +\tASSERT(static_cast<unsigned int>(low) < ARRAY_SIZE(camera_state_name) &&\n> +\t       static_cast<unsigned int>(high) < ARRAY_SIZE(camera_state_name));\n> +\n> +\tLOG(Camera, Debug) << \"Camera in \" << camera_state_name[state_]\n> +\t\t\t   << \" state trying operation requiring state between \"\n> +\t\t\t   << camera_state_name[low] << \" and \"\n> +\t\t\t   << camera_state_name[high];\n> +\n> +\treturn false;\n> +}\n> +\n> +bool Camera::stateIs(State state) const\n> +{\n> +\tif (state_ == state)\n> +\t\treturn true;\n> +\n> +\tASSERT(static_cast<unsigned int>(state) < ARRAY_SIZE(camera_state_name));\n> +\n> +\tLOG(Camera, Debug) << \"Camera in \" << camera_state_name[state_]\n> +\t\t\t   << \" state trying operation requiring state \"\n> +\t\t\t   << camera_state_name[state];\n> +\n> +\treturn false;\n> +}\n> +\n>  /**\n>   * \\brief Notify camera disconnection\n>   *\n> @@ -135,11 +249,24 @@ Camera::~Camera()\n>   * instance notifies the application by emitting the #disconnected signal, and\n>   * ensures that all new calls to the application-facing Camera API return an\n>   * error immediately.\n> + *\n> + * \\todo: Deal with pending requests if the camera is disconnected in a\n> + * running state.\n> + * \\todo: Update comment about Running state when importing buffers as well as\n> + * allocating them are supported.\n\nNo need for colons after any of the \\todo tags.\n\n>   */\n>  void Camera::disconnect()\n>  {\n>  \tLOG(Camera, Debug) << \"Disconnecting camera \" << name_;\n>  \n> +\t/*\n> +\t * If the camera was running when the hardware was removed force the\n> +\t * state to Prepared to allow applications to call freeBuffers() and\n> +\t * release() before deleting the camera.\n> +\t */\n> +\tif (state_ == CameraRunning)\n> +\t\tstate_ = CameraPrepared;\n> +\n>  \tdisconnected_ = true;\n>  \tdisconnected.emit(this);\n>  }\n> @@ -155,16 +282,24 @@ void Camera::disconnect()\n>   * Once exclusive access isn't needed anymore, the device should be released\n>   * with a call to the release() function.\n>   *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n\ns/effects/affects/ (through the whole file)\n\n> + *\n>   * \\todo Implement exclusive access across processes.\n>   *\n>   * \\return 0 on success or a negative error code otherwise\n> + * \\retval -ENODEV The camera has been disconnected from the system\n> + * \\retval -EBUSY The camera is not free and can't be acquired by the caller\n>   */\n>  int Camera::acquire()\n>  {\n> -\tif (acquired_)\n> +\tif (disconnected_)\n> +\t\treturn -ENODEV;\n> +\n> +\tif (!stateIs(CameraAvailable))\n>  \t\treturn -EBUSY;\n>  \n> -\tacquired_ = true;\n> +\tstate_ = CameraAcquired;\n> +\n>  \treturn 0;\n>  }\n>  \n> @@ -173,10 +308,20 @@ int Camera::acquire()\n>   *\n>   * Releasing the camera device allows other users to acquire exclusive access\n>   * with the acquire() function.\n> + *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + * \\retval -EBUSY The camera is running and can't be released\n>   */\n> -void Camera::release()\n> +int Camera::release()\n>  {\n> -\tacquired_ = false;\n> +\tif (!stateBetween(CameraAvailable, CameraConfigured))\n> +\t\treturn -EBUSY;\n> +\n> +\tstate_ = CameraAvailable;\n> +\n> +\treturn 0;\n>  }\n>  \n>  /**\n> @@ -235,18 +380,22 @@ Camera::streamConfiguration(std::set<Stream *> &streams)\n>   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n>   * to calling this function, otherwise an -EACCES error will be returned.\n>   *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n> + *\n>   * \\return 0 on success or a negative error code otherwise\n> - * \\retval -ENODEV The camera is not connected to any hardware\n> - * \\retval -EACCES The user has not acquired exclusive access to the camera\n> + * \\retval -ENODEV The camera has been disconnected from the system\n> + * \\retval -EACCES The camera is not in a state where it can be configured\n>   * \\retval -EINVAL The configuration is not valid\n>   */\n>  int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)\n>  {\n>  \tint ret;\n>  \n> -\tret = exclusiveAccess();\n> -\tif (ret)\n> -\t\treturn ret;\n> +\tif (disconnected_)\n> +\t\treturn -ENODEV;\n> +\n> +\tif (!stateBetween(CameraAvailable, CameraConfigured))\n> +\t\treturn -EACCES;\n>  \n>  \tif (!config.size()) {\n>  \t\tLOG(Camera, Error)\n> @@ -273,20 +422,28 @@ int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)\n>  \t\tstream->bufferPool().createBuffers(cfg.bufferCount);\n>  \t}\n>  \n> +\tstate_ = CameraConfigured;\n> +\n>  \treturn 0;\n>  }\n>  \n>  /**\n>   * \\brief Allocate buffers for all configured streams\n> + *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n> + *\n>   * \\return 0 on success or a negative error code otherwise\n> + * \\retval -ENODEV The camera has been disconnected from the system\n> + * \\retval -EACCES The camera is not in a state where buffers can be allocated\n> + * \\retval -EINVAL The configuration is not valid\n>   */\n>  int Camera::allocateBuffers()\n>  {\n> -\tint ret;\n> +\tif (disconnected_)\n> +\t\treturn -ENODEV;\n>  \n> -\tret = exclusiveAccess();\n> -\tif (ret)\n> -\t\treturn ret;\n> +\tif (!stateIs(CameraConfigured))\n> +\t\treturn -EACCES;\n>  \n>  \tif (activeStreams_.empty()) {\n>  \t\tLOG(Camera, Error)\n> @@ -295,7 +452,7 @@ int Camera::allocateBuffers()\n>  \t}\n>  \n>  \tfor (Stream *stream : activeStreams_) {\n> -\t\tret = pipe_->allocateBuffers(this, stream);\n> +\t\tint ret = pipe_->allocateBuffers(this, stream);\n>  \t\tif (ret) {\n>  \t\t\tLOG(Camera, Error) << \"Failed to allocate buffers\";\n>  \t\t\tfreeBuffers();\n> @@ -303,14 +460,24 @@ int Camera::allocateBuffers()\n>  \t\t}\n>  \t}\n>  \n> +\tstate_ = CameraPrepared;\n> +\n>  \treturn 0;\n>  }\n>  \n>  /**\n>   * \\brief Release all buffers from allocated pools in each stream\n> + *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + * \\retval -EACCES The camera is not in a state where buffers can be freed\n>   */\n> -void Camera::freeBuffers()\n> +int Camera::freeBuffers()\n>  {\n> +\tif (!stateIs(CameraPrepared))\n> +\t\treturn -EACCES;\n> +\n>  \tfor (Stream *stream : activeStreams_) {\n>  \t\tif (!stream->bufferPool().count())\n>  \t\t\tcontinue;\n> @@ -318,6 +485,10 @@ void Camera::freeBuffers()\n>  \t\tpipe_->freeBuffers(this, stream);\n>  \t\tstream->bufferPool().destroyBuffers();\n>  \t}\n> +\n> +\tstate_ = CameraConfigured;\n> +\n> +\treturn 0;\n>  }\n>  \n>  /**\n> @@ -333,7 +504,7 @@ void Camera::freeBuffers()\n>   */\n>  Request *Camera::createRequest()\n\nShould you update the documentation of this function ?\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\n>  {\n> -\tif (exclusiveAccess())\n> +\tif (disconnected_ || !stateBetween(CameraPrepared, CameraRunning))\n>  \t\treturn nullptr;\n>  \n>  \treturn new Request(this);\n> @@ -351,16 +522,18 @@ Request *Camera::createRequest()\n>   * automatically after it completes.\n>   *\n>   * \\return 0 on success or a negative error code otherwise\n> + * \\retval -ENODEV The camera has been disconnected from the system\n> + * \\retval -EACCES The camera is not running so requests can't be queued\n>   */\n>  int Camera::queueRequest(Request *request)\n>  {\n> -\tint ret;\n> +\tif (disconnected_)\n> +\t\treturn -ENODEV;\n>  \n> -\tret = exclusiveAccess();\n> -\tif (ret)\n> -\t\treturn ret;\n> +\tif (!stateIs(CameraRunning))\n> +\t\treturn -EACCES;\n>  \n> -\tret = request->prepare();\n> +\tint ret = request->prepare();\n>  \tif (ret) {\n>  \t\tLOG(Camera, Error) << \"Failed to prepare request\";\n>  \t\treturn ret;\n> @@ -376,17 +549,29 @@ int Camera::queueRequest(Request *request)\n>   * can queue requests to the camera to process and return to the application\n>   * until the capture session is terminated with \\a stop().\n>   *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n> + *\n>   * \\return 0 on success or a negative error code otherwise\n> + * \\retval -ENODEV The camera has been disconnected from the system\n> + * \\retval -EACCES The camera is not in a state where it can be started\n>   */\n>  int Camera::start()\n>  {\n> -\tint ret = exclusiveAccess();\n> -\tif (ret)\n> -\t\treturn ret;\n> +\tif (disconnected_)\n> +\t\treturn -ENODEV;\n> +\n> +\tif (!stateIs(CameraPrepared))\n> +\t\treturn -EACCES;\n>  \n>  \tLOG(Camera, Debug) << \"Starting capture\";\n>  \n> -\treturn pipe_->start(this);\n> +\tint ret = pipe_->start(this);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tstate_ = CameraRunning;\n> +\n> +\treturn 0;\n>  }\n>  \n>  /**\n> @@ -395,30 +580,27 @@ int Camera::start()\n>   * This method stops capturing and processing requests immediately. All pending\n>   * requests are cancelled and complete synchronously in an error state.\n>   *\n> + * This function effects the state of the camera, see \\ref camera_operation.\n> + *\n>   * \\return 0 on success or a negative error code otherwise\n> + * \\retval -ENODEV The camera has been disconnected from the system\n> + * \\retval -EACCES The camera is not running so can't be stopped\n>   */\n>  int Camera::stop()\n>  {\n> -\tint ret = exclusiveAccess();\n> -\tif (ret)\n> -\t\treturn ret;\n> +\tif (disconnected_)\n> +\t\treturn -ENODEV;\n> +\n> +\tif (!stateIs(CameraRunning))\n> +\t\treturn -EACCES;\n>  \n>  \tLOG(Camera, Debug) << \"Stopping capture\";\n>  \n> +\tstate_ = CameraPrepared;\n> +\n>  \tpipe_->stop(this);\n>  \n>  \treturn 0;\n>  }\n>  \n> -int Camera::exclusiveAccess()\n> -{\n> -\tif (disconnected_)\n> -\t\treturn -ENODEV;\n> -\n> -\tif (!acquired_)\n> -\t\treturn -EACCES;\n> -\n> -\treturn 0;\n> -}\n> -\n>  } /* namespace libcamera */","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0E111610B6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Feb 2019 22:39:54 +0100 (CET)","from pendragon.ideasonboard.com\n\t(dfj612yhrgyx302h3jwwy-3.rev.dnainternet.fi\n\t[IPv6:2001:14ba:21f5:5b00:ce28:277f:58d7:3ca4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 78B2149;\n\tThu, 28 Feb 2019 22:39:53 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1551389993;\n\tbh=LcHUwpGbEwpw55Fp76KFN+Pmo7DOf0GaGBFsA/UtkZI=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=WPSed0OLH+0ZPq3iRQZCX7Zbsa/JKThyJYddfX2pluU1kaHmta57lnVwB5+Od8rlO\n\tkkS4BqbpGQ0kIQewKv7gMqlbvvC2lMBDuBZq3/ZqUz42/9AzLrHPEffawXUAuwvUi4\n\tPqLka2zALo72hDjXQpPwiPg4iYr8lnsv3vfECYoQ=","Date":"Thu, 28 Feb 2019 23:39:47 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Niklas =?utf-8?q?S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190228213947.GJ7811@pendragon.ideasonboard.com>","References":"<20190228185126.32475-1-niklas.soderlund@ragnatech.se>\n\t<20190228185126.32475-4-niklas.soderlund@ragnatech.se>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190228185126.32475-4-niklas.soderlund@ragnatech.se>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v3 3/3] libcamera: camera: add state\n\tmachine to control access from applications","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Thu, 28 Feb 2019 21:39:54 -0000"}},{"id":978,"web_url":"https://patchwork.libcamera.org/comment/978/","msgid":"<20190228233729.GQ899@bigcity.dyn.berto.se>","date":"2019-02-28T23:37:29","subject":"Re: [libcamera-devel] [PATCH v3 3/3] libcamera: camera: add state\n\tmachine to control access from applications","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your feedback.\n\nOn 2019-02-28 23:39:47 +0200, Laurent Pinchart wrote:\n> Hi Niklas,\n> \n> Thank you for the patch.\n> \n> On Thu, Feb 28, 2019 at 07:51:26PM +0100, Niklas Söderlund wrote:\n> > There is a need to better control the order of operations an application\n> > performs on a camera for it to function correctly. Add a basic state\n> > machine to ensure applications perform operations on the camera in good\n> > order.\n> > \n> > Internal to the Camera states are added; Available, Acquired,\n> > Configured, Prepared and Running. Each state represents a higher state\n> > of configuration of the camera ultimately leading to the highest state\n> > where the camera is capturing frames. Each state supports a subset of\n> > operations the application may perform.\n> > \n> > Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> > ---\n> >  include/libcamera/camera.h |  18 ++-\n> >  src/libcamera/camera.cpp   | 266 +++++++++++++++++++++++++++++++------\n> >  2 files changed, 238 insertions(+), 46 deletions(-)\n> > \n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index 9c8ae01ed5c607f1..e5212cf05d221279 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -39,7 +39,7 @@ public:\n> >  \tSignal<Camera *> disconnected;\n> >  \n> >  \tint acquire();\n> > -\tvoid release();\n> > +\tint release();\n> >  \n> >  \tconst std::set<Stream *> &streams() const;\n> >  \tstd::map<Stream *, StreamConfiguration>\n> > @@ -47,7 +47,7 @@ public:\n> >  \tint configureStreams(std::map<Stream *, StreamConfiguration> &config);\n> >  \n> >  \tint allocateBuffers();\n> > -\tvoid freeBuffers();\n> > +\tint freeBuffers();\n> >  \n> >  \tRequest *createRequest();\n> >  \tint queueRequest(Request *request);\n> > @@ -56,20 +56,30 @@ public:\n> >  \tint stop();\n> >  \n> >  private:\n> > +\tenum State {\n> > +\t\tCameraAvailable,\n> > +\t\tCameraAcquired,\n> > +\t\tCameraConfigured,\n> > +\t\tCameraPrepared,\n> > +\t\tCameraRunning,\n> > +\t};\n> > +\n> >  \tCamera(PipelineHandler *pipe, const std::string &name);\n> >  \t~Camera();\n> >  \n> > +\tbool stateBetween(State low, State high) const;\n> > +\tbool stateIs(State state) const;\n> > +\n> >  \tfriend class PipelineHandler;\n> >  \tvoid disconnect();\n> > -\tint exclusiveAccess();\n> >  \n> >  \tstd::shared_ptr<PipelineHandler> pipe_;\n> >  \tstd::string name_;\n> >  \tstd::set<Stream *> streams_;\n> >  \tstd::set<Stream *> activeStreams_;\n> >  \n> > -\tbool acquired_;\n> >  \tbool disconnected_;\n> > +\tState state_;\n> >  };\n> >  \n> >  } /* namespace libcamera */\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index 84b97b5c2ce94ecf..0b06c0d838b356f3 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -11,6 +11,7 @@\n> >  \n> >  #include \"log.h\"\n> >  #include \"pipeline_handler.h\"\n> > +#include \"utils.h\"\n> >  \n> >  /**\n> >   * \\file camera.h\n> > @@ -42,6 +43,9 @@ LOG_DECLARE_CATEGORY(Camera)\n> >   * \\class Camera\n> >   * \\brief Camera device\n> >   *\n> > + * \\todo Add documentation for camera start timings. What exactly does the\n> > + * camera expect the pipeline handler to do when start() is called?\n> > + *\n> >   * The Camera class models a camera capable of producing one or more image\n> >   * streams from a single image source. It provides the main interface to\n> >   * configuring and controlling the device, and capturing image streams. It is\n> > @@ -52,6 +56,78 @@ LOG_DECLARE_CATEGORY(Camera)\n> >   * created with the create() function which returns a shared pointer. The\n> >   * Camera constructors and destructor are private, to prevent instances from\n> >   * being constructed and destroyed manually.\n> > + *\n> > + * \\section camera_operation Operating the Camera\n> > + *\n> > + * An application needs to perform a sequence of operations on a camera before\n> > + * it is ready to process requests. The camera needs to be acquired, configured\n> > + * and resources allocated or imported to prepare the camera for capture. Once\n> > + * started the camera can process requests until it is stopped. When an\n> > + * application is done with a camera all resources allocated needs to be freed\n> \n> s/needs/need/\n> \n> > + * and the camera released.\n> > + *\n> > + * An application may start and stop a camera multiple times as long as it is\n> > + * not released. The camera may also be reconfigured provided that all\n> > + * resources allocated are freed prior to the reconfiguration.\n> > + *\n> > + * \\subsection Camera States\n> > + *\n> > + * To help manage the sequence of operations needed to control the camera a set\n> > + * of states are defined. Each state describes which operations may be performed\n> > + * on the camera. Operations not listed in the state diagram are allowed in all\n> > + * states.\n> > + *\n> > + * \\dot\n> > + * digraph camera_state_machine {\n> > + *   node [shape = doublecircle ]; Available;\n> > + *   node [shape = circle ]; Acquired;\n> > + *   node [shape = circle ]; Configured;\n> > + *   node [shape = circle ]; Prepared;\n> > + *   node [shape = circle ]; Running;\n> > + *\n> > + *   Available -> Available [label = \"release()\"];\n> > + *   Available -> Acquired [label = \"acquire()\"];\n> > + *\n> > + *   Acquired -> Available [label = \"release()\"];\n> > + *   Acquired -> Configured [label = \"configureStreams()\"];\n> > + *\n> > + *   Configured -> Available [label = \"release()\"];\n> > + *   Configured -> Configured [label = \"configureStreams()\"];\n> > + *   Configured -> Prepared [label = \"allocateBuffers()\"];\n> > + *\n> > + *   Prepared -> Configured [label = \"freeBuffers()\"];\n> > + *   Prepared -> Prepared [label = \"createRequest()\"];\n> > + *   Prepared -> Running [label = \"start()\"];\n> > + *\n> > + *   Running -> Prepared [label = \"stop()\"];\n> > + *   Running -> Running [label = \"createRequest(), queueRequest()\"];\n> > + * }\n> > + * \\enddot\n> > + *\n> > + * \\subsubsection Available\n> > + * The base state of a camera, an application can inspect the properties of the\n> > + * camera to determine if it wishes to use it. If an application wishes to use\n> > + * a camera it should acquire() it to proceed to the Acquired state.\n> > + *\n> > + * \\subsubsection Acquired\n> > + * In the acquired state an application has exclusive access to the camera and\n> > + * may modify the camera's parameters to configure it and proceed to the\n> > + * Configured state.\n> > + *\n> > + * \\subsubsection Configured\n> > + * The camera is configured and ready for the application to prepare it with\n> > + * resources. The camera may be reconfigured multiple times until resources\n> > + * are provided and the state progresses to Prepared.\n> > + *\n> > + * \\subsubsection Prepared\n> > + * The camera has been configured and provided with resources and is ready to be\n> > + * started. The application may free the camera's resources to get back to the\n> > + * Configured state or start it to progress to the Running state.\n> \n> s/start/start()/\n> \n> > + *\n> > + * \\subsubsection Running\n> > + * The camera is running and ready to process requests queued by the\n> > + * application. The camera remains in this state until it is stopped and moved\n> > + * to the Prepared state.\n> >   */\n> >  \n> >  /**\n> > @@ -116,17 +192,55 @@ const std::string &Camera::name() const\n> >   */\n> >  \n> >  Camera::Camera(PipelineHandler *pipe, const std::string &name)\n> > -\t: pipe_(pipe->shared_from_this()), name_(name), acquired_(false),\n> > -\t  disconnected_(false)\n> > +\t: pipe_(pipe->shared_from_this()), name_(name), disconnected_(false),\n> > +\t  state_(CameraAvailable)\n> >  {\n> >  }\n> >  \n> >  Camera::~Camera()\n> >  {\n> > -\tif (acquired_)\n> > +\tif (!stateIs(CameraAvailable))\n> >  \t\tLOG(Camera, Error) << \"Removing camera while still in use\";\n> >  }\n> >  \n> > +static const char *const camera_state_name[] = {\n> \n> Maybe camera_state_names ?\n> \n> > +\t\"Available\",\n> > +\t\"Acquired\",\n> > +\t\"Configured\",\n> > +\t\"Prepared\",\n> > +\t\"Running\",\n> > +};\n> > +\n> > +bool Camera::stateBetween(State low, State high) const\n> > +{\n> > +\tif (state_ >= low && state_ <= high)\n> > +\t\treturn true;\n> > +\n> > +\tASSERT(static_cast<unsigned int>(low) < ARRAY_SIZE(camera_state_name) &&\n> > +\t       static_cast<unsigned int>(high) < ARRAY_SIZE(camera_state_name));\n> > +\n> > +\tLOG(Camera, Debug) << \"Camera in \" << camera_state_name[state_]\n> > +\t\t\t   << \" state trying operation requiring state between \"\n> > +\t\t\t   << camera_state_name[low] << \" and \"\n> > +\t\t\t   << camera_state_name[high];\n> > +\n> > +\treturn false;\n> > +}\n> > +\n> > +bool Camera::stateIs(State state) const\n> > +{\n> > +\tif (state_ == state)\n> > +\t\treturn true;\n> > +\n> > +\tASSERT(static_cast<unsigned int>(state) < ARRAY_SIZE(camera_state_name));\n> > +\n> > +\tLOG(Camera, Debug) << \"Camera in \" << camera_state_name[state_]\n> > +\t\t\t   << \" state trying operation requiring state \"\n> > +\t\t\t   << camera_state_name[state];\n> > +\n> > +\treturn false;\n> > +}\n> > +\n> >  /**\n> >   * \\brief Notify camera disconnection\n> >   *\n> > @@ -135,11 +249,24 @@ Camera::~Camera()\n> >   * instance notifies the application by emitting the #disconnected signal, and\n> >   * ensures that all new calls to the application-facing Camera API return an\n> >   * error immediately.\n> > + *\n> > + * \\todo: Deal with pending requests if the camera is disconnected in a\n> > + * running state.\n> > + * \\todo: Update comment about Running state when importing buffers as well as\n> > + * allocating them are supported.\n> \n> No need for colons after any of the \\todo tags.\n> \n> >   */\n> >  void Camera::disconnect()\n> >  {\n> >  \tLOG(Camera, Debug) << \"Disconnecting camera \" << name_;\n> >  \n> > +\t/*\n> > +\t * If the camera was running when the hardware was removed force the\n> > +\t * state to Prepared to allow applications to call freeBuffers() and\n> > +\t * release() before deleting the camera.\n> > +\t */\n> > +\tif (state_ == CameraRunning)\n> > +\t\tstate_ = CameraPrepared;\n> > +\n> >  \tdisconnected_ = true;\n> >  \tdisconnected.emit(this);\n> >  }\n> > @@ -155,16 +282,24 @@ void Camera::disconnect()\n> >   * Once exclusive access isn't needed anymore, the device should be released\n> >   * with a call to the release() function.\n> >   *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> \n> s/effects/affects/ (through the whole file)\n> \n> > + *\n> >   * \\todo Implement exclusive access across processes.\n> >   *\n> >   * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -ENODEV The camera has been disconnected from the system\n> > + * \\retval -EBUSY The camera is not free and can't be acquired by the caller\n> >   */\n> >  int Camera::acquire()\n> >  {\n> > -\tif (acquired_)\n> > +\tif (disconnected_)\n> > +\t\treturn -ENODEV;\n> > +\n> > +\tif (!stateIs(CameraAvailable))\n> >  \t\treturn -EBUSY;\n> >  \n> > -\tacquired_ = true;\n> > +\tstate_ = CameraAcquired;\n> > +\n> >  \treturn 0;\n> >  }\n> >  \n> > @@ -173,10 +308,20 @@ int Camera::acquire()\n> >   *\n> >   * Releasing the camera device allows other users to acquire exclusive access\n> >   * with the acquire() function.\n> > + *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> > + *\n> > + * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -EBUSY The camera is running and can't be released\n> >   */\n> > -void Camera::release()\n> > +int Camera::release()\n> >  {\n> > -\tacquired_ = false;\n> > +\tif (!stateBetween(CameraAvailable, CameraConfigured))\n> > +\t\treturn -EBUSY;\n> > +\n> > +\tstate_ = CameraAvailable;\n> > +\n> > +\treturn 0;\n> >  }\n> >  \n> >  /**\n> > @@ -235,18 +380,22 @@ Camera::streamConfiguration(std::set<Stream *> &streams)\n> >   * Exclusive access to the camera shall be ensured by a call to acquire() prior\n> >   * to calling this function, otherwise an -EACCES error will be returned.\n> >   *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> > + *\n> >   * \\return 0 on success or a negative error code otherwise\n> > - * \\retval -ENODEV The camera is not connected to any hardware\n> > - * \\retval -EACCES The user has not acquired exclusive access to the camera\n> > + * \\retval -ENODEV The camera has been disconnected from the system\n> > + * \\retval -EACCES The camera is not in a state where it can be configured\n> >   * \\retval -EINVAL The configuration is not valid\n> >   */\n> >  int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)\n> >  {\n> >  \tint ret;\n> >  \n> > -\tret = exclusiveAccess();\n> > -\tif (ret)\n> > -\t\treturn ret;\n> > +\tif (disconnected_)\n> > +\t\treturn -ENODEV;\n> > +\n> > +\tif (!stateBetween(CameraAvailable, CameraConfigured))\n> > +\t\treturn -EACCES;\n> >  \n> >  \tif (!config.size()) {\n> >  \t\tLOG(Camera, Error)\n> > @@ -273,20 +422,28 @@ int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)\n> >  \t\tstream->bufferPool().createBuffers(cfg.bufferCount);\n> >  \t}\n> >  \n> > +\tstate_ = CameraConfigured;\n> > +\n> >  \treturn 0;\n> >  }\n> >  \n> >  /**\n> >   * \\brief Allocate buffers for all configured streams\n> > + *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> > + *\n> >   * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -ENODEV The camera has been disconnected from the system\n> > + * \\retval -EACCES The camera is not in a state where buffers can be allocated\n> > + * \\retval -EINVAL The configuration is not valid\n> >   */\n> >  int Camera::allocateBuffers()\n> >  {\n> > -\tint ret;\n> > +\tif (disconnected_)\n> > +\t\treturn -ENODEV;\n> >  \n> > -\tret = exclusiveAccess();\n> > -\tif (ret)\n> > -\t\treturn ret;\n> > +\tif (!stateIs(CameraConfigured))\n> > +\t\treturn -EACCES;\n> >  \n> >  \tif (activeStreams_.empty()) {\n> >  \t\tLOG(Camera, Error)\n> > @@ -295,7 +452,7 @@ int Camera::allocateBuffers()\n> >  \t}\n> >  \n> >  \tfor (Stream *stream : activeStreams_) {\n> > -\t\tret = pipe_->allocateBuffers(this, stream);\n> > +\t\tint ret = pipe_->allocateBuffers(this, stream);\n> >  \t\tif (ret) {\n> >  \t\t\tLOG(Camera, Error) << \"Failed to allocate buffers\";\n> >  \t\t\tfreeBuffers();\n> > @@ -303,14 +460,24 @@ int Camera::allocateBuffers()\n> >  \t\t}\n> >  \t}\n> >  \n> > +\tstate_ = CameraPrepared;\n> > +\n> >  \treturn 0;\n> >  }\n> >  \n> >  /**\n> >   * \\brief Release all buffers from allocated pools in each stream\n> > + *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> > + *\n> > + * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -EACCES The camera is not in a state where buffers can be freed\n> >   */\n> > -void Camera::freeBuffers()\n> > +int Camera::freeBuffers()\n> >  {\n> > +\tif (!stateIs(CameraPrepared))\n> > +\t\treturn -EACCES;\n> > +\n> >  \tfor (Stream *stream : activeStreams_) {\n> >  \t\tif (!stream->bufferPool().count())\n> >  \t\t\tcontinue;\n> > @@ -318,6 +485,10 @@ void Camera::freeBuffers()\n> >  \t\tpipe_->freeBuffers(this, stream);\n> >  \t\tstream->bufferPool().destroyBuffers();\n> >  \t}\n> > +\n> > +\tstate_ = CameraConfigured;\n> > +\n> > +\treturn 0;\n> >  }\n> >  \n> >  /**\n> > @@ -333,7 +504,7 @@ void Camera::freeBuffers()\n> >   */\n> >  Request *Camera::createRequest()\n> \n> Should you update the documentation of this function ?\n> \n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nThanks, I have addressed all your comments, collected your tag and \npushed. Thanks for all your hard work reviewing, especially the \ndocumentation bits.\n\n> \n> >  {\n> > -\tif (exclusiveAccess())\n> > +\tif (disconnected_ || !stateBetween(CameraPrepared, CameraRunning))\n> >  \t\treturn nullptr;\n> >  \n> >  \treturn new Request(this);\n> > @@ -351,16 +522,18 @@ Request *Camera::createRequest()\n> >   * automatically after it completes.\n> >   *\n> >   * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -ENODEV The camera has been disconnected from the system\n> > + * \\retval -EACCES The camera is not running so requests can't be queued\n> >   */\n> >  int Camera::queueRequest(Request *request)\n> >  {\n> > -\tint ret;\n> > +\tif (disconnected_)\n> > +\t\treturn -ENODEV;\n> >  \n> > -\tret = exclusiveAccess();\n> > -\tif (ret)\n> > -\t\treturn ret;\n> > +\tif (!stateIs(CameraRunning))\n> > +\t\treturn -EACCES;\n> >  \n> > -\tret = request->prepare();\n> > +\tint ret = request->prepare();\n> >  \tif (ret) {\n> >  \t\tLOG(Camera, Error) << \"Failed to prepare request\";\n> >  \t\treturn ret;\n> > @@ -376,17 +549,29 @@ int Camera::queueRequest(Request *request)\n> >   * can queue requests to the camera to process and return to the application\n> >   * until the capture session is terminated with \\a stop().\n> >   *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> > + *\n> >   * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -ENODEV The camera has been disconnected from the system\n> > + * \\retval -EACCES The camera is not in a state where it can be started\n> >   */\n> >  int Camera::start()\n> >  {\n> > -\tint ret = exclusiveAccess();\n> > -\tif (ret)\n> > -\t\treturn ret;\n> > +\tif (disconnected_)\n> > +\t\treturn -ENODEV;\n> > +\n> > +\tif (!stateIs(CameraPrepared))\n> > +\t\treturn -EACCES;\n> >  \n> >  \tLOG(Camera, Debug) << \"Starting capture\";\n> >  \n> > -\treturn pipe_->start(this);\n> > +\tint ret = pipe_->start(this);\n> > +\tif (ret)\n> > +\t\treturn ret;\n> > +\n> > +\tstate_ = CameraRunning;\n> > +\n> > +\treturn 0;\n> >  }\n> >  \n> >  /**\n> > @@ -395,30 +580,27 @@ int Camera::start()\n> >   * This method stops capturing and processing requests immediately. All pending\n> >   * requests are cancelled and complete synchronously in an error state.\n> >   *\n> > + * This function effects the state of the camera, see \\ref camera_operation.\n> > + *\n> >   * \\return 0 on success or a negative error code otherwise\n> > + * \\retval -ENODEV The camera has been disconnected from the system\n> > + * \\retval -EACCES The camera is not running so can't be stopped\n> >   */\n> >  int Camera::stop()\n> >  {\n> > -\tint ret = exclusiveAccess();\n> > -\tif (ret)\n> > -\t\treturn ret;\n> > +\tif (disconnected_)\n> > +\t\treturn -ENODEV;\n> > +\n> > +\tif (!stateIs(CameraRunning))\n> > +\t\treturn -EACCES;\n> >  \n> >  \tLOG(Camera, Debug) << \"Stopping capture\";\n> >  \n> > +\tstate_ = CameraPrepared;\n> > +\n> >  \tpipe_->stop(this);\n> >  \n> >  \treturn 0;\n> >  }\n> >  \n> > -int Camera::exclusiveAccess()\n> > -{\n> > -\tif (disconnected_)\n> > -\t\treturn -ENODEV;\n> > -\n> > -\tif (!acquired_)\n> > -\t\treturn -EACCES;\n> > -\n> > -\treturn 0;\n> > -}\n> > -\n> >  } /* namespace libcamera */\n> \n> -- \n> Regards,\n> \n> Laurent Pinchart","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x243.google.com (mail-lj1-x243.google.com\n\t[IPv6:2a00:1450:4864:20::243])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 637E9610B3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  1 Mar 2019 00:37:31 +0100 (CET)","by mail-lj1-x243.google.com with SMTP id t13so14379469lji.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Feb 2019 15:37:31 -0800 (PST)","from localhost (89-233-230-99.cust.bredband2.com. [89.233.230.99])\n\tby smtp.gmail.com with ESMTPSA id\n\te18sm4212705ljb.2.2019.02.28.15.37.29\n\t(version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256);\n\tThu, 28 Feb 2019 15:37:29 -0800 (PST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to\n\t:user-agent; bh=b01JfJ9S+rvX3fudsleOAqzqAUXuN4zjNRZe9StKGno=;\n\tb=KYv/H2gKULRP9tB+N0HdNKj/XIgEtVV18vrfBaHGYsQ2MLNFhyK1F+hjQgqrXogNic\n\tO+uwKB+nCzqfMNDgPKV81HVxJP+TiGky9TQA4dFhojy7XEUTxZb1iZeSgAEQIsskkcI9\n\tFwS1LKscVo5AuAfu9eAGdT+WEhc1/kk0mfkC3Sd7mF7OIPK+0/KyrP1naP3Pb2pluIfE\n\tMr8YVWKTgrwZq4LsL1MN9Ox6F7GI0fb62uQthmhxmlFEqRDId77z/rtEJDRSfP9lMi8x\n\tWvOu9EdEup4OMJ0A5ItqY8Uu9QBJBcyM5Y87co5tvotzn59r8/wYhwCdfuIMeK65lHSi\n\tnWqw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to:user-agent;\n\tbh=b01JfJ9S+rvX3fudsleOAqzqAUXuN4zjNRZe9StKGno=;\n\tb=bvSxeJKFYc9AlqW9qzoiJ3JAqk+fD8TALQ0ckjBWF37736gq5FUdXLrsvbp2qnvWD7\n\tF1U5H+O72w+pFGMGLmM7EHlOTvTklPYYa2RfGjZiLhkrtYYA3/WntmstbJGpBc1rQULD\n\t+fhRbUry668QfdioZFOeLHH6XxvNHWoSbI1xbydNcEGfH1d3IXC/EG7XcpVgUS9FXyxH\n\tcbR/U8ztFKbGIq9OkZ7cXIFmGDojOSafanqO/CGIQpCkQowfhMCT2oYV0GiXof1wHX9o\n\tWZsdhE5ZhJ9/7NotpIVtYyIr6iMZdAGiVPmN2QjjqqoJumEdDGX2RH7aOPK3UyODxhmO\n\txs3Q==","X-Gm-Message-State":"APjAAAURKRoGjfy7QY6ISazSxfN1ktkeVaIAlwybRsVGxjiXlY9zdC0V\n\t+2AvuYCXyCpVLLAfMewYNZEA0M9jOjY=","X-Google-Smtp-Source":"APXvYqz5UeykEZ8MeaIS6Wfbkx8WJiW49tYsiJ9nTdqsLtOKrGDuylIRDNd0ae1WbP3VINeb1+ZHoA==","X-Received":"by 2002:a2e:97ce:: with SMTP id m14mr843092ljj.162.1551397050626;\n\tThu, 28 Feb 2019 15:37:30 -0800 (PST)","Date":"Fri, 1 Mar 2019 00:37:29 +0100","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190228233729.GQ899@bigcity.dyn.berto.se>","References":"<20190228185126.32475-1-niklas.soderlund@ragnatech.se>\n\t<20190228185126.32475-4-niklas.soderlund@ragnatech.se>\n\t<20190228213947.GJ7811@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190228213947.GJ7811@pendragon.ideasonboard.com>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v3 3/3] libcamera: camera: add state\n\tmachine to control access from applications","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Thu, 28 Feb 2019 23:37:31 -0000"}}]