From 2e4df9d72f0ddb2b47de57f431858c4aa4037ba8 Mon Sep 17 00:00:00 2001 From: Dongju Chae Date: Thu, 27 Jun 2019 19:05:41 +0900 Subject: [PATCH] [N2] The prototype of host handler This commit adds the implementation of host handler. Signed-off-by: Dongju Chae --- core/npu-engine/src/ne-comm.h | 48 ++-- core/npu-engine/src/ne-handler.c | 480 +++++++++++++++++++++++++++++++++++++++ core/npu-engine/src/ne-handler.h | 32 +++ 3 files changed, 540 insertions(+), 20 deletions(-) create mode 100644 core/npu-engine/src/ne-handler.c create mode 100644 core/npu-engine/src/ne-handler.h diff --git a/core/npu-engine/src/ne-comm.h b/core/npu-engine/src/ne-comm.h index 08e792c..c4892dc 100644 --- a/core/npu-engine/src/ne-comm.h +++ b/core/npu-engine/src/ne-comm.h @@ -22,9 +22,10 @@ #ifndef NE_COMM_H__ #define NE_COMM_H__ -#include "../../../common/include/typedef.h" +#include #include "ne-mem.h" #include "ne-model.h" +#include "ne-common.h" /** * @brief N2. Host Handler registers its callbacks with this struct @@ -39,12 +40,12 @@ typedef struct { /** * @brief Host tells us to register a model * @param[in] mem The hwmem block/chunk with the model from the host. - * @param[out] errno Set with error-number if there is an error + * @param[out] err Set with error-number if there is an error * @return The model representation. Null if error. * * @detail ADDR-M in the Activity Sequence is the retval->mem_model; */ - model *(*registerModel) (hwmem *mem, int *errno); + model *(*registerModel) (hwmem *mem, int *err); /** * @brief Host tells us to unregister a model @@ -57,37 +58,44 @@ typedef struct { /** * @brief Host tells us to start an operation. * @param[in] op The operation mode + * @param[in] force if non-zero, try to stop/start with preemption + * @param[in] m the model to be activated * @param[in] cb The callback to be called when the output is ready * @param[in] cb_data The private data to be given to the callback. - * @param[in] current_buffer The buffer with the input loaded. For internal-input (cam/mic) mode, this is the first input buffer for the internal input services. + * @param[in] current_buffer The buffer with the input loaded. For internal-input (cam/mic) mode, this is the first input buffer for the internal input services. (TBD) + * @return 0 if ok. errno if error. */ - int (*setOpMode) (npu_input_opmode op, output_ready cb, void *cb_data, buffer *current_buffer); + int (*setOpMode) (npu_input_opmode op, bool force, model *m, output_ready cb, + void *cb_data, buffer *current_buffer); /** - * @brief With double buffering, returns the next buffer and - * invalidate the returned buffer. - * @param[in] m The model used. - * @param[in] invalidate If !0, invalidate the contents of the buffer - * so that NPU core will not process it until we - * validate the buffer. (anti-tearing) - * @param[out] errno Set with error-number if there is an error - * @return The buffer for input data writing. NULL if error. - * - * @detail If both buffers are being used, we cannot invalidate and - * return a buffer. It may happen time to time. - * Depending on the parameters from libNPUHost, the caller (N1) + * @brief get the next input buffer + * @param[out] err Set with error-number if there is an error + * @return the buffer for input data writing. NULL if error. + * @detail Depending on the parameters from libNPUHost, the caller (N1) * may wait and retry or return error. * The return value is "ADDR-I1" in Activity Sequence. */ - buffer *(*getBuffer) (model *m, int invalidate, int *errno); + buffer *(*getCurrentInputBuffer) (int *err); + + + /** + * @brief get the next output buffer + * @param[out] err Set with error-number if there is an error + * @return the buffer for output data to be transferred. NULL if error. + * @detail Depending on the parameters from libNPUHost, the caller (N1) + * may wait and retry or return error. + */ + buffer *(*getCurrentOutputBuffer) (int *err); /** * @brief The buffer is filled and valid for inference. Start when ready. - * @param[in] m The model used. * @param[in] buffer The buffer with input data filled. * @return 0 if ok. errno if error. + * + * @note after validation, it's no longer accessible because it was returned. */ - int (*validateBuffer)(model *m, buffer *buffer); + int (*validateBuffer)(buffer *buffer); } hostHandlerInfo; diff --git a/core/npu-engine/src/ne-handler.c b/core/npu-engine/src/ne-handler.c new file mode 100644 index 0000000..deee56d --- /dev/null +++ b/core/npu-engine/src/ne-handler.c @@ -0,0 +1,480 @@ +/** + * Proprietary + * Copyright (C) 2019 Samsung Electronics + * Copyright (C) 2019 Dongju Chae + */ +/** + * @file NE-handler.c + * @date 25 Jun 2019 + * @brief Host (model) handler for NPU Engine (NE). + * @see http://suprem.sec.samsung.net/confluence/display/ODLC/Software+Stack + * @author Dongju Chae + * @bug No known bugs except for NYI items + */ + +#include "ne-handler.h" +#include "ne-mem.h" +#include "ne-comm.h" +#include "ne-conf.h" +#include "ne-scheduler.h" + +#include +#include +#include +#include + +#define TAG _N2 + +/** + * @brief private data structure for host handler + */ +typedef struct { + uint64_t total_size; + uint64_t model_size; + uint64_t buffer_size; + uint32_t num_models; + uint32_t model_id; + output_ready n1_cb; + pthread_mutex_t mutex; + list model_priv_list; +} handler_priv; + +/** +* @brief static instance of private data structure for host handler +*/ +static handler_priv hpriv; + +/** + * @brief private data structure for model + */ +typedef struct { + model model; + npubin_meta meta; + hwmem *hwmem; + list_node list; +} model_priv; + +typedef struct { + submodel submodel; + npubin_submeta meta; +} submodel_priv; + +/** + * @brief a global lock for host handler + */ +#define HANDLER_LOCK() pthread_mutex_lock(&hpriv.mutex) +#define HANDLER_UNLOCK() pthread_mutex_unlock(&hpriv.mutex) + +/** + * @brief get a (sub-)model's private data + */ +#define GET_MODEL_PRIVATE(m) CONTAINER_OF(m, model_priv, model); +#define GET_SUBMODEL_PRIVATE(m) CONTAINER_OF(m, submodel_priv, submodel); + +/** + * @brief get the maximum buffer size for registerd models + * @note the lock is already acquired in the outside + * @return the maximum buffer size + */ +static uint64_t +get_maximum_buffer_size (void) +{ + uint64_t maximum = 0; + model_priv *mpriv; + + list_for_each_entry (mpriv, hpriv.model_priv_list, list) { + if (maximum < mpriv->meta.buffer_size) + maximum = mpriv->meta.buffer_size; + } + + return maximum; +} + +/** + * @brief free resources of all submodels + * @param[in] model the model instance + * @note the lock is already acquired in the outside + */ +static void +free_all_submodels (model* m) +{ + submodel *sm, *tmp; + + list_for_each_entry_safe (sm, tmp, m->submodels, list) { + submodel_priv *spriv = GET_SUBMODEL_PRIVATE (sm); + + list_del (&m->submodels, &sm->list); + assert (&spriv->submodel == sm); + free (spriv); + } +} + +/** + * @brief free resourcs of all models + */ +static void +free_all_models (void) +{ + model_priv *mpriv, *tmp; + + list_for_each_entry_safe (mpriv, tmp, hpriv.model_priv_list, list) { + list_del (&hpriv.model_priv_list, &mpriv->list); + free_all_submodels (&mpriv->model); + free (mpriv); + + hpriv.num_models--; + } +} + +/** + * @brief parse model binray data to create submodel instances + * @param[in] mpriv model's private data + * @param[in] binary model binary + * @return 0 if no error, otherwise a negative errno + * @note the lock is already acquired in the outside + */ +static int +parse_model_meta (model_priv* mpriv, void *binary) +{ + model* model = &mpriv->model; + npubin_meta *meta = &mpriv->meta; + uint64_t offset = 0; + uint32_t num_submodels; + uint32_t i; + + memcpy (meta, binary + offset, NPUBIN_META_SIZE); + offset += NPUBIN_META_SIZE; + + num_submodels = meta->num_submodels; + for (i = 0; i < num_submodels; i++) { + submodel_priv *spriv; + submodel *smodel; + npubin_submeta *smeta; + + spriv = (submodel_priv *) malloc (sizeof (submodel_priv)); + if (!smodel) + goto err_nomem; + + smodel = &spriv->submodel; + smeta = &spriv->meta; + + memcpy (smeta, binary + offset, NPUBIN_META_SIZE); + + smodel->meta = smeta; + smodel->parent = model; + smodel->offset_absolute = offset; + + list_add (&model->submodels, &smodel->list); + + /* size includes submeta size as well */ + offset += smeta->size; + } + + return 0; + +err_nomem: + /* free previous allocations */ + free_all_submodels (model); + return -ENOMEM; +} + +/** + * @brief get available model memory size + * @return the available memory size + * + * @note even if there is enough memory space, model registration can be failed + * because it doesn't mean that such a space is always physically contiguous. + * Also, we cannot gaurantee the memory compaction is always succesful. + */ +static uint64_t +handler_get_available_model_memory (void) +{ + uint64_t available_size; + + HANDLER_LOCK (); + + assert (hpriv.total_size >= hpriv.model_size + hpriv.buffer_size); + + available_size = hpriv.total_size - hpriv.model_size - hpriv.buffer_size; + + HANDLER_UNLOCK (); + + return available_size; +} + +/** + * @brief get a number of registered models + * @return a number of registered models + */ +static uint32_t +handler_get_num_registered_models (void) +{ + return hpriv.num_models; +} + +/** + * @brief create and register model with hwmem + * @param[in] hwmem hwmem which contains model data + * @param[out] err + * @return new intance of model + */ +static model* +handler_register_model (hwmem *hwmem, int *err) +{ + model_priv *mpriv; + model *new_model = NULL; + void *ptr; + + HANDLER_LOCK (); + + mpriv = (model_priv *) malloc (sizeof (model_priv)); + if (!mpriv) { + *err = -ENOMEM; + goto err_unlock; + } + + mpriv->hwmem = hwmem; + new_model = &mpriv->model; + new_model->meta = &mpriv->meta; + + if (hpriv.buffer_size < new_model->meta->buffer_size) { + /* need to increase buffer size! */ + if (GET_MEM()->resize_buffers (new_model->meta->buffer_size) != 0) { + /* fail to resize... */ + *err = -ENOMEM; + goto err_free; + } + } + + /* activate hwmem; it means that it's not compacted until unregistration */ + if ((*err = hwmem_activate (hwmem)) < 0) + goto err_free; + + if ((*err = hwmem_get_data (hwmem, &ptr)) < 0) + goto err_deactivate; + + list_init (&new_model->submodels); + if ((*err = parse_model_meta (mpriv, ptr)) < 0) + goto err_deactivate; + + new_model->memblock = hwmem; + new_model->model_size = hwmem->size; + new_model->inputSequence = 0; + + hpriv.model_size += hwmem->size; + hpriv.num_models++; + + list_add (&hpriv.model_priv_list, &mpriv->list); + + *err = 0; + + HANDLER_UNLOCK (); + + return new_model; + +/* rollback activation */ +err_deactivate: + hwmem_deactivate (hwmem); +err_free: + free (mpriv); +err_unlock: + HANDLER_UNLOCK (); + + return NULL; +} + +/** + * @brief unregister model + * @param[in] model the intance of model that is no longer used + * @return 0 if no error, otherwise a negative errno + * @note in this function, model's hwmem is deallocated. + */ +static int +handler_unregister_model (model *model) +{ + model_priv *mpriv; + int err = 0; + + if (!model) { + logerr (TAG, "Empty model (NULL) provided\n"); + return -EINVAL; + } + + HANDLER_LOCK (); + + mpriv = GET_MODEL_PRIVATE (model); + + if ((err = hwmem_deactivate (mpriv->hwmem)) < 0) + goto err_unlock; + + if ((err = GET_MEM()->dealloc (mpriv->hwmem)) < 0) + goto err_activate; + + free_all_submodels (model); + list_del (&hpriv.model_priv_list, &mpriv->list); + + if ((err = GET_MEM()->resize_buffers (get_maximum_buffer_size ())) < 0) + goto err_activate; + + free (mpriv); + + assert (hpriv.num_models > 0); + + hpriv.num_models--; + +/* rollback deactivation */ +err_activate: + hwmem_activate (mpriv->hwmem); +err_unlock: + HANDLER_UNLOCK (); + + return err; +} + +/** @brief N2's callback wrapper for output ready */ +static void +n2_cb (buffer *buf, uint64_t offset, uint64_t size, void *data) +{ + /* TODO do pre-handler */ + hpriv.n1_cb (buf, offset, size, data); + /* TODO do post-handler */ +} + +/** + * @brief Host tells us to start an operation. + * @param[in] op The operation mode + * @param[in] force if non-zero, try to stop/start with preemption + * @param[in] m the model to be activated + * @param[in] cb The callback to be called when the output is ready + * @param[in] cb_data The private data to be given to the callback. + * @param[in] current_buffer The buffer with the input loaded. For internal-input (cam/mic) mode, this is the first input buffer for the internal input services. (TBD) + * @return 0 if ok. errno if error. + */ +static int +handler_set_op_mode (npu_input_opmode op, bool force, model* m, output_ready cb, + void *cb_data, buffer *current_buffer) +{ + HANDLER_LOCK (); + + hpriv.n1_cb = cb; + + HANDLER_UNLOCK (); + + return setOpMode (op, force, m, n2_cb, cb_data, current_buffer); +} + +/** + * @brief get the next input buffer + * @param[out] err Set with error-number if there is an error + * @return the buffer for input data writing. NULL if error. + * @detail Depending on the parameters from libNPUHost, the caller (N1) + * may wait and retry or return error. + * The return value is "ADDR-I1" in Activity Sequence. + */ +static buffer* +handler_get_current_input_buffer (int *err) +{ + return GET_MEM()->get_next_buffer(BUFFER_ROLE_INPUT, err); +} + +/** + * @brief get the next output buffer + * @param[out] err Set with error-number if there is an error + * @return the buffer for output data to be transferred. NULL if error. + * @detail Depending on the parameters from libNPUHost, the caller (N1) + * may wait and retry or return error. + */ +static buffer* +handler_get_current_output_buffer (int *err) +{ + return GET_MEM()->get_next_buffer(BUFFER_ROLE_OUTPUT, err); +} + +/** + * @brief The buffer is filled and valid for inference. Start when ready. + * @param[in] buffer The buffer with input data filled. + * @return 0 if ok. errno if error. + * + * @note after validation, it's no longer accessible because it was returned. + */ +static int +handler_validate_buffer (buffer *buffer) +{ + if (!buffer) { + logerr (TAG, "Empty buffer (NULL) provided\n"); + return -EINVAL; + } + + return GET_MEM()->return_buffer (buffer); +} + +/** + * @brief the instance of host handler. Its callbacks are called in N1. + */ +static hostHandlerInfo handler = { + .getAvailableModelMemory = handler_get_available_model_memory, + .getNumRegisteredModels = handler_get_num_registered_models, + .registerModel = handler_register_model, + .unregisterModel = handler_unregister_model, + .setOpMode = handler_set_op_mode, + .getCurrentInputBuffer = handler_get_current_input_buffer, + .getCurrentOutputBuffer = handler_get_current_output_buffer, + .validateBuffer = handler_validate_buffer, +}; + +/** + * @brief initilize host handler + * @return 0 if no error, otherwise a negative errno + * @note it should be called in main() + */ +int +init_ne_handler (void) +{ + int err; + + if (conf == NULL) { + logerr (TAG, "Configuraion is not loaded yet\n"); + return -ENOENT; + } + + if ((err = GET_MEM()->init (conf->reserved_mem_size, + &handler.modelmemorysize)) < 0) { + logerr (TAG, "Fail to initialize the memory allocator\n"); + return err; + } + + /* if init() is successful, the returned size should be > 0 */ + assert (handler.modelmemorysize > 0); + + hpriv.total_size = handler.modelmemorysize; + hpriv.model_size = 0; + hpriv.buffer_size = 0; + + hpriv.num_models = 0; + hpriv.model_id = 0; + + list_init (&hpriv.model_priv_list); + pthread_mutex_init (&hpriv.mutex, NULL); + + return initNEcomm (&handler, conf->communication_method); +} + +/** + * @brief terminate host handler + * @return 0 if no error, otherwise a negative errno + * @note it should be called in main() + */ +int +exit_ne_handler (void) +{ + int err; + + free_all_models(); + + pthread_mutex_destroy (&hpriv.mutex); + + if ((err = exitNEcomm ()) < 0) + return err; + + return GET_MEM()->fini (); +} diff --git a/core/npu-engine/src/ne-handler.h b/core/npu-engine/src/ne-handler.h new file mode 100644 index 0000000..20e6639 --- /dev/null +++ b/core/npu-engine/src/ne-handler.h @@ -0,0 +1,32 @@ +/** + * Proprietary + * Copyright (C) 2019 Samsung Electronics + * Copyright (C) 2019 Dongju Chae + */ +/** + * @file NE-handler.h + * @date 25 Jun 2019 + * @brief Host (model) handler for NPU Engine (NE). + * @see http://suprem.sec.samsung.net/confluence/display/ODLC/Software+Stack + * @author Dongju Chae + * @bug No known bugs except for NYI items + */ + +#ifndef __NPU_ENGINE_HANDLER_H__ +#define __NPU_ENGINE_HANDLER_H__ + +/** + * @brief initilize host handler + * @return 0 if no error, otherwise a negative errno + * @note it should be called in main() + */ +int init_ne_handler (void); + +/** + * @brief terminate host handler + * @return 0 if no error, otherwise a negative errno + * @note it should be called in main() + */ +int exit_ne_handler (void); + +#endif /* __NPU_ENGINE_HANDLER_H__ */ -- 2.7.4