[UAPI] Set model's npubinfmt version
[platform/adaptation/npu/trix-engine.git] / src / core / ne-handler.cc
1 /**
2  * Proprietary
3  * Copyright (C) 2020 Samsung Electronics
4  * Copyright (C) 2020 Dongju Chae <dongju.chae@samsung.com>
5  */
6 /**
7  * @file ne-handler.cc
8  * @date 03 Apr 2020
9  * @brief Impelemetation of NPU Engine entrypoint that handles APIs from host
10  * @see https://code.sec.samsung.net/confluence/display/ODLC/2020+Overall+Software+Stack
11  * @author Dongju Chae <dongju.chae@samsung.com>
12  * @bug No known bugs except for NYI items
13  */
14
15 #include "ne-handler.h"
16 #include "ne-data.h"
17
18 #include <npubinfmt.h>
19 #include <NPUdrvAPI.h>
20 #include <CommPlugin.h>
21
22 #include <string.h>
23 #include <assert.h>
24
25 #include <condition_variable>
26 #include <functional>
27 #include <atomic>
28 #include <map>
29
30 #define TAG _N2
31
32 /** @brief host handler constructor */
33 HostHandler::HostHandler (Device *device)
34   : device_(device),
35     /* ignored as we don't use double buffering anymore, but for backward-compatibility */
36     async_mode_ (NPUASYNC_WAIT)
37 {
38 }
39
40 /** @brief host handler destructor */
41 HostHandler::~HostHandler ()
42 {
43 }
44
45 /**
46  * @brief register model from generic buffer
47  * @param[in] model_buf model buffer
48  * @param[out] modelid model id
49  * @return 0 if no error. otherwise a negative errno
50  */
51 int
52 HostHandler::registerModel (generic_buffer *model_buf, uint32_t *modelid)
53 {
54   if (model_buf == nullptr || modelid == nullptr) {
55     logerr (TAG, "Invalid arguments given\n");
56     return -EINVAL;
57   }
58
59   Model *model = nullptr;
60   int status = device_->setModel (model_buf, &model);
61   if (status != 0) {
62     logerr (TAG, "Failed to set model: %d\n", status);
63     return status;
64   }
65
66   assert (model != nullptr);
67
68   status = models_.insert (model->getID(), model);
69   if (status != 0) {
70     logerr (TAG, "Failed to insert model id\n");
71     delete model;
72     return status;
73   }
74
75   *modelid = model->getID();
76   return 0;
77 }
78
79 /**
80  * @brief remove the registered model
81  * @param[in] modelid model id
82  * @return 0 if no error. otherwise a negative errno
83  */
84 int
85 HostHandler::unregisterModel (uint32_t modelid)
86 {
87   Model *model = models_.find (modelid);
88   if (model == nullptr)
89     return -ENOENT;
90
91   int status = device_->unsetModel (model);
92   if (status != 0) {
93     logerr (TAG, "Failed to unset model: %d\n", status);
94     return status;
95   }
96
97   return models_.remove (modelid);
98 }
99
100 /**
101  * @brief remove all registered models
102  * @return 0
103  */
104 int
105 HostHandler::unregisterModels ()
106 {
107   std::function <bool (Model *)> functor =
108     [&] (Model *m) -> bool {
109       bool can_remove = true;
110       int status = device_->unsetModel (m);
111       if (status != 0) {
112         logwarn (TAG, "Failed to unset model: %d\n", status);
113         can_remove = false;
114       }
115       return can_remove;
116     };
117
118   models_.for_each (functor);
119   return 0;
120 }
121
122 /**
123  * @brief Get the profile information from NPU
124  * @param[in] task_id The identifier for each inference
125  * @param[out] profile The profile instance
126  * @return 0 if no error, otherwise a negative errno.
127  */
128 int
129 HostHandler::getProfile (int task_id, npu_profile *profile)
130 {
131   if (task_id < 0 || profile == nullptr) {
132     logerr (TAG, "Invalid parameter provided\n");
133     return -EINVAL;
134   }
135
136   const DriverAPI * api = device_->getDriverAPI ();
137   assert (api != nullptr);
138
139   profile->num_layers = 0;
140   profile->layers = nullptr;
141
142   int status = api->getProfile (task_id, profile);
143   if (status != 0) {
144     logerr (TAG, "Failed to get profile information: %d\n", status);
145     return status;
146   }
147
148   return 0;
149 }
150
151 /**
152  * @brief get the stats for the latest apps of the target device
153  * @param[out] stat The list of app stat
154  * @note The caller has the responsibility to free the resources.
155  *       This API is not working on the emulated envionment.
156  */
157 int
158 HostHandler::getStatApps (npu_stat_apps *stat)
159 {
160   const DriverAPI * api = device_->getDriverAPI ();
161   assert (api != nullptr);
162
163   return api->getStatApps (stat);
164 }
165
166 /**
167  * @brief get the stats for the latest tasks of the target app
168  * @param[in] appid The identifier of target app
169  * @param[out] stat The list of task stat
170  * @note The caller has the responsibility to free the resources.
171  *       This API is not working on the emulated envionment.
172  */
173 int
174 HostHandler::getStatTasks (int appid, npu_stat_tasks *stat)
175 {
176   const DriverAPI * api = device_->getDriverAPI ();
177   assert (api != nullptr);
178
179   return api->getStatTasks (appid, stat);
180 }
181
182 /**
183  * @brief Get the driver API level of opened NPU device
184  * @param[out] level driver API level
185  * @return 0 if no error, otherwise a negative errno
186  */
187 int
188 HostHandler::getAPILevel (uint32_t *level)
189 {
190   const DriverAPI * api = device_->getDriverAPI ();
191   assert (api != nullptr);
192
193   return api->getAPILevel (level);
194 }
195
196 /**
197  * @brief Get the TOPS of the opened NPU device
198  * @param[in] dev the NPU device handle
199  * @param[out] tops npu tops
200  * @return 0 if no error, otherwise a negative errno
201  * @note this does not support for emulated devices
202  */
203 int
204 HostHandler::getTops (uint32_t *tops)
205 {
206   const DriverAPI * api = device_->getDriverAPI ();
207   assert (api != nullptr);
208
209   return api->getTops (tops);
210 }
211
212 /**
213  * @brief Set the data layout for input/output tensors
214  * @param[in] modelid The ID of model whose layouts are set
215  * @param[in] in the layout/type info for input tensors
216  * @param[in] out the layout/type info for output tensors
217  * @return @c 0 if no error. otherwise a negative error value
218  * @note if this function is not called, default layout/type will be used.
219  */
220 int
221 HostHandler::setDataInfo (uint32_t modelid, tensors_data_info *in,
222     tensors_data_info *out)
223 {
224   Model *model = models_.find (modelid);
225   if (model == nullptr)
226     return -ENOENT;
227
228   return model->setDataInfo (in, out);
229 }
230
231 /**
232  * @brief Set the inference constraint for next NPU inferences
233  * @param[in] modelid The target model id
234  * @param[in] constraint inference constraint (e.g., timeout, priority)
235  * @return @c 0 if no error. otherwise a negative error value
236  * @note If this function is not called, default values are used.
237  */
238 int
239 HostHandler::setConstraint (uint32_t modelid, npuConstraint constraint)
240 {
241   Model *model = models_.find (modelid);
242   if (model == nullptr)
243     return -ENOENT;
244
245   model->setConstraint (constraint);
246
247   return 0;
248 }
249
250 /**
251  * @brief find and return model instance
252  * @param[in] modelid model id
253  * @return model instance if found. otherwise nullptr
254  */
255 Model *
256 HostHandler::getModel (uint32_t modelid)
257 {
258   return models_.find (modelid);
259 }
260
261 /** @brief dummay callback for runSync. */
262 class callbackSync {
263   public:
264     callbackSync (output_buffers *output) : output_(output), done_(false) {}
265
266     static void callback (output_buffers *output, uint64_t sequence, void *data) {
267       callbackSync *sync = static_cast<callbackSync *>(data);
268       sync->callback (output, sequence);
269     }
270
271     void callback (output_buffers *output, uint64_t sequence) {
272       if (output_ != nullptr) {
273         /** just copy internal variables of output buffers */
274         memcpy (output_, output, sizeof (output_buffers));
275       }
276       done_ = true;
277       cv_.notify_one ();
278     }
279
280     void wait () {
281       std::unique_lock<std::mutex> lock (m_);
282       cv_.wait (lock, [this]() { return done_; });
283     }
284
285   private:
286     std::mutex m_;
287     std::condition_variable cv_;
288     output_buffers *output_;
289     bool done_;
290 };
291
292 /**
293  * @brief Execute inference. Wait (block) until the output is available.
294  * @param[in] modelid The model to be inferred.
295  * @param[in] input The input data to be inferred.
296  * @param[out] output The output result.
297  * @return @c 0 if no error. otherwise a negative error value
298  */
299 int
300 HostHandler::runSync (uint32_t modelid, const input_buffers *input,
301     output_buffers *output)
302 {
303   callbackSync sync (output);
304   int status = runAsync (modelid, input, callbackSync::callback,
305       static_cast <void*> (&sync), NPUASYNC_DROP_OLD, nullptr);
306   if (status == 0) {
307     /** sync needs to wait callback */
308     sync.wait ();
309   }
310   return status;
311 }
312
313 /**
314  * @brief Invoke NPU inference. Unblocking call.
315  * @param[in] modelid The model to be inferred.
316  * @param[in] input The input data to be inferred.
317  * @param[in] cb The output buffer handler.
318  * @param[in] cb_data The data given as a parameter to the runNPU_async call.
319  * @param[in] mode Configures how this operation works.
320  * @param[out] sequence The sequence number returned with runNPU_async.
321  * @return @c 0 if no error. otherwise a negative error value
322  */
323 int
324 HostHandler::runAsync (uint32_t modelid, const input_buffers *input,
325     npuOutputNotify cb, void *cb_data, npu_async_mode mode, uint64_t *sequence)
326 {
327   Model *model = nullptr;
328
329   if (device_->needModel()) {
330     model = getModel (modelid);
331     if (model == nullptr)
332       return -ENOENT;
333   }
334
335   /* check the given model before running */
336   if (model != nullptr && !model->finalize ()) {
337     logerr (TAG, "Failed to finalize the model. Please see the log messages\n");
338     return -EINVAL;
339   }
340
341   device_->setAsyncMode (mode);
342   return device_->run (NPUINPUT_HOST, model, input, cb, cb_data, sequence);
343 }
344
345 /**
346  * @brief Let NPU accept input frames from its internal source continuously
347  * @param[in] modelid The model to be inferred.
348  * @param[in] opmode NPU has different opmode with auto-inputs. Choose one.
349  * @param[in] hw_dev The target device feeding input data
350  * @return @c 0 if no error. otherwise a negative error value
351  */
352 int
353 HostHandler::runInternal (uint32_t modelid, npu_input_opmode opmode,
354     std::string hw_dev)
355 {
356   Model *model = nullptr;
357
358   if (device_->needModel()) {
359     model = getModel (modelid);
360     if (model == nullptr)
361       return -ENOENT;
362   }
363
364   /* check the given model before running */
365   if (model != nullptr && !model->finalize ()) {
366     logerr (TAG, "Failed to finalize the model. Please see the log messages\n");
367     return -EINVAL;
368   }
369
370   return device_->runInternal (opmode, model, hw_dev);
371 }
372
373 /**
374  * @brief Stop the request with the given id
375  * @param[in] dev The NPU device handle
376  * @param[in] id The request id
377  * @return @c 0 if no error. otherwise a negative error value
378  */
379 int
380 HostHandler::stopInternal (int id)
381 {
382   if (id <= 0) {
383     logerr (TAG, "Unable to stop this request with id (%d)\n", id);
384     return -EINVAL;
385   }
386
387   const DriverAPI * api = device_->getDriverAPI ();
388   assert (api != nullptr);
389
390   return api->stop_target (id);
391 }
392
393 /**
394  * @brief get number of available devices
395  * @param[in] type device type
396  * @return number of devices
397  */
398 int
399 HostHandler::getNumDevices (dev_type type)
400 {
401   return DriverAPI::getNumDevices (type);
402 }
403
404 /**
405  * @brief get device instance
406  * @param[out] dev device instance
407  * @param[in] type device type
408  * @param[in] id device id
409  * @return 0 if no error. otherwise a negative errno
410  */
411 int
412 HostHandler::getDevice (npudev_h *dev, dev_type type, uint32_t id)
413 {
414   int num_devices = getNumDevices (type);
415
416   /** check the validity of device id */
417   if (!(num_devices > 0 && id < static_cast<uint32_t>(num_devices))) {
418     logerr (TAG, "Invalid arguments provided\n");
419     return -ENODEV;
420   }
421
422   Device *device = Device::createInstance (type, id);
423   if (device == nullptr) {
424     logerr (TAG, "Failed to create a device with the given type\n");
425     return -EINVAL;
426   }
427
428   *dev = device;
429
430   return 0;
431 }
432
433 /**
434  * @brief allocate generic buffer (just for users)
435  * @param[out] buffer buffer instance
436  * @return 0 if no error. otherwise a negative errno
437  */
438 int
439 HostHandler::allocGenericBuffer (generic_buffer *buffer)
440 {
441   if (buffer == NULL)
442     return -EINVAL;
443
444   if (buffer->size == 0) {
445     logerr (TAG, "Invalid size\n");
446     return -EINVAL;
447   }
448
449   if (buffer->size > UINT32_MAX) {
450     logerr (TAG, "Don't support such a large size");
451     return -ENOMEM;
452   }
453
454   switch (buffer->type) {
455     case BUFFER_FILE:
456       /* nothing to do */
457       if (buffer->filepath == nullptr)
458         return -EINVAL;
459       break;
460     case BUFFER_MAPPED:
461     {
462       /* now, npu-engine always provides dmabuf-based allocation */
463       void *addr = nullptr;
464       int dmabuf = device_->allocMemory (buffer->size, &addr);
465       if (dmabuf < 0)
466         return dmabuf;
467
468       buffer->dmabuf = dmabuf;
469       buffer->offset = 0;
470       buffer->addr = addr;
471     } break;
472     default:
473       return -EINVAL;
474   }
475
476   return 0;
477 }
478
479 /**
480  * @brief deallocate generic buffer (just for users)
481  * @param[in] buffer buffer instance
482  * @return 0 if no error. otherwise a negative errno
483  */
484 int
485 HostHandler::deallocGenericBuffer (generic_buffer *buffer)
486 {
487   if (buffer == NULL)
488     return -EINVAL;
489
490   switch (buffer->type) {
491     case BUFFER_FILE:
492       /** always true cuz nothing to do */
493       break;
494     case BUFFER_MAPPED:
495       return device_->deallocMemory (buffer->dmabuf, buffer->size, buffer->addr);
496     default:
497       return -EINVAL;
498   }
499
500   return 0;
501 }
502
503 /**
504  * @brief allocate multiple generic buffers (just for users)
505  * @param[out] buffers multi-buffer instance
506  * @return 0 if no error. otherwise a negative errno
507  */
508 int
509 HostHandler::allocGenericBuffer (generic_buffers *buffers)
510 {
511   uint32_t idx;
512   int status = 0;
513
514   if (buffers == NULL || buffers->num_buffers < 1)
515     return -EINVAL;
516
517   for (idx = 0; idx < buffers->num_buffers; idx++) {
518     status = allocGenericBuffer (&buffers->bufs[idx]);
519     if (status != 0)
520       goto free_buffer;
521   }
522
523   return 0;
524
525 free_buffer:
526   while (idx != 0) {
527     deallocGenericBuffer (&buffers->bufs[--idx]);
528   }
529
530   return status;
531 }
532
533 /**
534  * @brief deallocate multiple generic buffers (just for users)
535  * @param[in] buffers multi-buffer instance
536  * @return 0 if no error. otherwise a negative errno
537  */
538 int
539 HostHandler::deallocGenericBuffer (generic_buffers *buffers)
540 {
541   if (buffers == NULL || buffers->num_buffers < 1)
542     return -EINVAL;
543
544   for (uint32_t idx = 0; idx < buffers->num_buffers; idx++)
545     deallocGenericBuffer (&buffers->bufs[idx]);
546   buffers->num_buffers = 0;
547
548   return 0;
549 }
550
551 /**
552  * @brief get the current memory status
553  * @param[out] alloc_total The size of allocated memory until now
554  * @param[out] free_total The size of freed memory until now
555  * @return 0 if no error. otherwise a negatice error value
556  */
557 int
558 HostHandler::getMemoryStatus (size_t *alloc_total, size_t *free_total)
559 {
560   /** API is always set in initialize () */
561   const DriverAPI * api = device_->getDriverAPI ();
562   assert (api != nullptr);
563
564   return api->getMemoryStatus (alloc_total, free_total);
565 }
566
567 /**
568  * @brief Get the current device status to be used
569  * @param[out] status the device status
570  * @param[out] num_requests the number of running requests (or pending)
571  * @return 0 if no error, otherwise a negative errno.
572  */
573 int
574 HostHandler::getDeviceStatus (npu_status *status, uint32_t *num_requests)
575 {
576   /** API is always set in initialize () */
577   const DriverAPI * api = device_->getDriverAPI ();
578
579   if (!api)
580     return -EINVAL;
581
582   device_state_t state = api->isReady ();
583   if (state == device_state_t::STATE_READY) {
584     *num_requests = api->numRequests ();
585     if (*num_requests > 0)
586       *status = NPU_READY;
587     else
588       *status = NPU_IDLE;
589   } else {
590     *num_requests = 0;
591     *status = NPU_ERROR;
592   }
593
594   return 0;
595 }
596
597 /** implement methods of Device class */
598
599 /** @brief constructor of device */
600 Device::Device (dev_type type, int id, bool need_model)
601   : comm_ (CommPlugin::getCommPlugin()), type_ (type), id_ (id), need_model_ (true),
602     mode_ (NPUASYNC_WAIT), initialized_ (false), atomic_flag_ (ATOMIC_FLAG_INIT)
603 {
604 }
605
606 /**
607  * @brief create device instance depending on device type and id
608  * @param[in] type device type
609  * @param[in] id device id
610  * @return device instance
611  */
612 Device *
613 Device::createInstance (dev_type type, int id)
614 {
615   Device *device = nullptr;
616
617   switch (type & DEVICETYPE_MASK) {
618     case DEVICETYPE_TRIV:
619       device = new TrinityVision (id);
620       break;
621     case DEVICETYPE_TRIV2:
622       device = new TrinityVision2 (id);
623       break;
624     case DEVICETYPE_TRIA:
625       device = new TrinityAsr (id);
626       device->setNeedModel (false);
627       break;
628     default:
629       break;
630   }
631
632   if (device != nullptr && device->init () != 0) {
633     delete device;
634     device = nullptr;
635   }
636
637   return device;
638 }
639
640 /**
641  * @brief device initialization
642  * @return 0 if no error, otherwise a negative errno
643  * @note Init failures come from createDriverAPI() only.
644  */
645 int
646 Device::init ()
647 {
648   /** should be initilizaed only once */
649   if (!atomic_flag_.test_and_set()) {
650     /** create the corresponding driver API */
651     api_ = DriverAPI::createDriverAPI (type_, id_);
652     if (api_.get() == nullptr) {
653       atomic_flag_.clear();
654       logerr (TAG, "Failed to create driver API\n");
655       return -EINVAL;
656     }
657
658     handler_.reset (new HostHandler (this));
659     scheduler_.reset (new Scheduler (api_.get()));
660     mem_ = MemAllocator::createInstance (api_.get());
661
662     initialized_ = true;  /** c++11 does not provide test() of atomic flag */
663   }
664
665   return 0;
666 }
667
668 /**
669  * @brief stop all requests from this device
670  * @param[in] force_stop indicate the schedduler waits until to handle previous requests
671  * @return 0 if no error, otherwise a negative errno
672  */
673 int
674 Device::stop (bool force_stop)
675 {
676   if (!initialized ()) {
677     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
678     return -EPERM;
679   }
680
681   Request *req = new Request (NPUINPUT_STOP);
682   req->setForceStop (force_stop);
683   return scheduler_->submitRequest (req);
684 }
685
686 /**
687  * @brief allocate generic memory buffer
688  * @param[in] size the size to allocate
689  * @param[out] addr the mapped address
690  * @return dmabuf fd if no error, otherwise a negative errno
691  */
692 int
693 Device::allocMemory (size_t size, void **addr)
694 {
695   if (!initialized ()) {
696     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
697     return -EPERM;
698   }
699
700   if (size == 0 || addr == nullptr) {
701     logerr (TAG, "Invalid arguments\n");
702     return -EINVAL;
703   }
704
705   return mem_->allocMemory (size, addr);
706 }
707
708 /**
709  * @brief deallocate generic memory buffer
710  * @param[in] dmabuf_fd dmabuf file descriptor
711  * @param[in] size buffer size
712  * @param[in] addr mapped addr
713  * @return 0 if no error, otherwise a negative errno
714  */
715 int
716 Device::deallocMemory (int dmabuf_fd, size_t size, void * addr)
717 {
718   if (!initialized ()) {
719     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
720     return -EPERM;
721   }
722
723   if (dmabuf_fd < 0 || size == 0 || addr == nullptr) {
724     logerr (TAG, "Invalid arguments\n");
725     return -EINVAL;
726   }
727
728   return mem_->deallocMemory (dmabuf_fd, size, addr);
729 }
730
731 /**
732  * @brief extract the buffer instance from input generic buffers
733  * @param[in] meta the model metadata
734  * @param[in] input the input generic buffers
735  * @return the buffer instance
736  */
737 Buffer *
738 TrinityVision::prepareInputBuffers (const Metadata *meta, const input_buffers *input)
739 {
740   if (meta == nullptr || input == nullptr ||
741       meta->getInputNum() != input->num_buffers) {
742     logerr (TAG, "Invalid metadata info provided\n");
743     return nullptr;
744   }
745
746   Buffer * buffer;
747   const generic_buffer *first = &input->bufs[0];
748   if (first->type == BUFFER_DMABUF) {
749     buffer = mem_->allocBuffer (new HWmemExternal);
750     if (buffer == nullptr)
751       return nullptr;
752
753     buffer->setDmabuf (first->dmabuf);
754     buffer->setOffset (first->offset);
755     buffer->setSize (meta->getBufferSize());
756   } else {
757     buffer = mem_->allocBuffer (new HWmemDevice);
758     if (buffer == nullptr)
759       return nullptr;
760
761     int status = buffer->alloc (meta->getBufferSize ());
762     if (status != 0) {
763       logerr (TAG, "Failed to allocate buffer: %d\n", status);
764       delete buffer;
765       return nullptr;
766     }
767   }
768
769   int status = buffer->createTensors (meta);
770   if (status != 0) {
771     logerr (TAG, "Failed to create tensors: %d\n", status);
772     delete buffer;
773     buffer = nullptr;
774   }
775
776   return buffer;
777 }
778
779 /**
780  * @brief implementation of TRIV's setModel ()
781  * @param[in] model_buf the model generic buffer
782  * @param[out] model the model instance
783  * @return 0 if no error, otherwise a negative errno
784  */
785 int
786 TrinityVision::setModel (const generic_buffer *model_buf, Model ** model_ptr)
787 {
788   if (!initialized ()) {
789     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
790     return -EPERM;
791   }
792
793   if (model_buf == nullptr || model_ptr == nullptr)
794     return -EINVAL;
795
796   Model *model = nullptr;
797   HWmem * hwmem_prog = nullptr;
798   HWmem * hwmem_weight = nullptr;
799   int status;
800
801   /** In TRIV1, model data (including program/weight) should be contiguous */
802
803   switch (model_buf->type) {
804   case BUFFER_FILE:
805   case BUFFER_MAPPED:
806     model = mem_->allocModel (new HWmemDevice);
807     if (model == nullptr) {
808       logerr (TAG, "Failed to allocate model\n");
809       return -ENOMEM;
810     }
811
812     status = model->alloc (model_buf->size);
813     if (status != 0) {
814       logerr (TAG, "Failed to allocate model: %d\n", status);
815       goto delete_exit;
816     }
817
818     /** extract the whole model data */
819     status = comm_.extractGenericBuffer (model_buf, model->getData(), nullptr);
820     if (status != 0) {
821       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
822       goto delete_exit;
823     }
824     break;
825   default:
826     return -EINVAL;
827   }
828
829   status = model->setMetadata (model->getData());
830   if (status != 0)
831     goto delete_exit;
832
833   /** allocate program (optional; NOP) */
834   if (model->getMetadata()->getProgramSize() > 0) {
835     hwmem_prog = new HWmem (new HWmemChunk);
836     model->setProgramData (hwmem_prog);
837
838     hwmem_prog->setParent (model);
839     hwmem_prog->setOffset (model->getMetadata()->getMetaSize());
840     status = hwmem_prog->alloc (model->getMetadata()->getProgramSize());
841     if (status != 0) {
842       logerr (TAG, "Failed to allocate program\n");
843       goto delete_exit;
844     }
845   }
846
847   /** allocate weight (optional) */
848   if (model->getMetadata()->getWeightSize() > 0) {
849     hwmem_weight = new HWmem (new HWmemChunk);
850     model->setWeightData (hwmem_weight);
851
852     hwmem_weight->setParent (model);
853     hwmem_weight->setOffset (model->getMetadata()->getMetaSize() +
854         model->getMetadata()->getProgramSize());
855     status = hwmem_weight->alloc (model->getMetadata()->getWeightSize());
856     if (status != 0) {
857       logerr (TAG, "Failed to allocate program\n");
858       goto delete_exit;
859     }
860   }
861
862   if (hwmem_prog != nullptr) {
863     /** register this model to the driver */
864     model_config_t config;
865     config.version = model->getMetadata()->getVersion ();
866     config.dbuf_fd = hwmem_prog->getDmabuf ();
867     config.program_size = hwmem_prog->getSize ();
868     config.program_offset_addr = hwmem_prog->getOffset ();
869     if (hwmem_weight != nullptr)
870       config.weight_offset_addr = hwmem_weight->getOffset ();
871
872     status = api_->registerModel (&config);
873     if (status != 0)
874       goto delete_exit;
875
876     model->setInternalID(config.id);
877   }
878
879   *model_ptr = model;
880   return status;
881
882 delete_exit:
883   delete model;
884   return status;
885 }
886
887 /**
888  * @brief implementation of TRIV's unsetModel ()
889  * @param[in] model the model instance
890  * @return 0 if no error, otherwise a negative errno
891  */
892 int
893 TrinityVision::unsetModel (Model * model)
894 {
895   if (!initialized ()) {
896     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
897     return -EPERM;
898   }
899
900   if (model == nullptr) {
901     logerr (TAG, "Invalid model instance\n");
902     return -EINVAL;
903   }
904
905   if (model->getMetadata()->getProgramSize() > 0)
906     return api_->deregisterModel (model->getInternalID ());
907
908   return 0;
909 }
910
911 /**
912  * @brief implementation of TRIV's run()
913  * @param[in] opmode input opmode
914  * @param[in] model the model instance
915  * @param[in] input generic buffers of input data
916  * @param[in] cb the output callback
917  * @param[in] cb_data the output callback data
918  * @param[out] sequence The sequence number returned with runNPU_async.
919  */
920 int
921 TrinityVision::run (npu_input_opmode opmode, const Model *model,
922     const input_buffers *input, npuOutputNotify cb, void *cb_data,
923     uint64_t *sequence)
924 {
925   if (!initialized ()) {
926     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
927     return -EPERM;
928   }
929
930   if (opmode != NPUINPUT_HOST) {
931     logerr (TAG, "TRIV supports only host inputservice\n");
932     return -EINVAL;
933   }
934
935   if (model == nullptr || input == nullptr) {
936     logerr (TAG, "TRIV requires both model and input buffers\n");
937     return -EINVAL;
938   }
939
940   const_cast<Model *>(model)->updateDataInfo ();
941
942   Buffer *buffer = prepareInputBuffers (model->getMetadata(), input);
943   if (buffer == nullptr) {
944     logerr (TAG, "Failed to extract buffer instance\n");
945     return -EINVAL;
946   }
947
948   if (!buffer->isExternal ()) {
949     for (uint32_t idx = 0; idx < input->num_buffers; idx++) {
950       auto func = std::bind (TrinityVision::manipulateData, model, idx, true,
951           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
952       int status = comm_.extractGenericBuffer (&input->bufs[idx],
953           buffer->getInputTensor(idx)->getData(), func);
954       if (status != 0) {
955         logerr (TAG, "Failed to feed input buffer: %d\n", status);
956         return status;
957       }
958     }
959   }
960
961   /** this device uses CMA buffer */
962
963   Request *req = new Request (opmode);
964   req->setModel (model);
965   req->setBuffer (buffer);
966
967   if (cb != nullptr)
968     req->setCallback (std::bind (&TrinityVision::callback, this, req, cb, cb_data));
969
970   if (sequence != nullptr)
971     *sequence = req->getID();
972
973   return scheduler_->submitRequest (req);
974 }
975
976 /**
977  * @brief callback of TRIV2 request
978  * @param[in] req the request instance
979  * @param[in] cb callback for completion
980  * @param[in] cb_data callback data
981  * @note The callback invoke does not gurantee the request was successful
982  * @todo Check the request failures
983  */
984 void
985 TrinityVision::callback (Request *req, npuOutputNotify cb, void *cb_data)
986 {
987   const Model *model = req->getModel ();
988   Buffer *buffer = req->getBuffer ();
989   output_buffers output = {
990     .num_buffers = buffer->getOutputNum ()
991   };
992
993   for (uint32_t idx = 0; idx < output.num_buffers; idx++) {
994     uint32_t output_tensor_size = model->getOutputTensorSize (idx);
995
996     if (buffer->isExternal ()) {
997       output.bufs[idx].type = BUFFER_DMABUF;
998       output.bufs[idx].size = output_tensor_size;
999       output.bufs[idx].addr = buffer->getOutputTensor(idx)->getData();
1000     } else {
1001       output.bufs[idx].type = BUFFER_MAPPED;
1002       output.bufs[idx].size = output_tensor_size;
1003       /** user needs to free this */
1004       output.bufs[idx].addr = malloc (output_tensor_size);
1005
1006       auto func = std::bind (TrinityVision::manipulateData, model, idx, false,
1007           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
1008       int status = comm_.insertGenericBuffer (buffer->getOutputTensor(idx)->getData(),
1009           &output.bufs[idx], func);
1010       if (status != 0) {
1011         logerr (TAG, "Failed to return output buffer: %d\n", status);
1012       }
1013     }
1014   }
1015
1016   cb (&output, req->getID(), cb_data);
1017
1018   delete buffer;
1019 }
1020
1021 /**
1022  * @brief extract the segment table instance from input generic buffers
1023  * @param[in] model the model instance
1024  * @param[in] input the input generic buffers
1025  * @param[in] output the output generic buffers
1026  * @return the segment table instance
1027  */
1028 SegmentTable *
1029 TrinityVision2::prepareSegmentTable (const Model *model, const input_buffers *input,
1030     const output_buffers *output)
1031 {
1032   const Metadata *meta = model->getMetadata ();
1033   if (meta == nullptr || (input != nullptr &&
1034         meta->getInputNum() != input->num_buffers)) {
1035     logerr (TAG, "Invalid metadata info provided\n");
1036     return nullptr;
1037   }
1038
1039   SegmentTable * segt = mem_->allocSegmentTable (new HWmemDevice);
1040   int status = segt->alloc ();
1041   if (status != 0) {
1042     logerr (TAG, "Failed to allocate segment table: %d\n", status);
1043     goto delete_segt;
1044   }
1045
1046   status = segt->createSegments (model, input, output);
1047   if (status != 0) {
1048     logerr (TAG, "Failed to create segments: %d\n", status);
1049     goto delete_segt;
1050   }
1051
1052   return segt;
1053
1054 delete_segt:
1055   delete segt;
1056   return nullptr;
1057 }
1058
1059 /**
1060  * @brief implementation of TRIV2's setModel ()
1061  * @param[in] model_buf the model generic buffer
1062  * @param[out] model the model instance
1063  * @return 0 if no error, otherwise a negative errno
1064  */
1065 int
1066 TrinityVision2::setModel (const generic_buffer *model_buf, Model ** model_ptr)
1067 {
1068   if (!initialized ()) {
1069     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1070     return -EPERM;
1071   }
1072
1073   if (model_buf == nullptr || model_ptr == nullptr)
1074     return -EINVAL;
1075
1076   Model *model;
1077   int status;
1078
1079   switch (model_buf->type) {
1080   case BUFFER_FILE:
1081   case BUFFER_MAPPED:
1082     model = mem_->allocModel (new HWmemDevice);
1083     if (model == nullptr) {
1084       logerr (TAG, "Failed to allocate model\n");
1085       return -ENOMEM;
1086     }
1087
1088     status = model->alloc (NPUBIN_META_SIZE);
1089     if (status != 0) {
1090       logerr (TAG, "Failed to allocate model: %d\n", status);
1091       goto delete_exit;
1092     }
1093
1094     status = comm_.extractGenericBuffer (model_buf, model->getData(), nullptr,
1095         0, NPUBIN_META_SIZE);
1096     if (status != 0) {
1097       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
1098       goto delete_exit;
1099     }
1100     break;
1101   default:
1102     return -EINVAL;
1103   }
1104
1105   status = model->setMetadata (model->getData());
1106   if (status != 0)
1107     goto delete_exit;
1108
1109   /** allocate program (optional; NOP) */
1110   if (model->getMetadata()->getProgramSize() > 0) {
1111     HWmem * hwmem_prog = new HWmem (new HWmemDevice);
1112     hwmem_prog->setDriverAPI (api_.get());
1113
1114     model->setProgramData (hwmem_prog);
1115
1116     status = hwmem_prog->alloc (model->getMetadata()->getProgramSize());
1117     if (status != 0) {
1118       logerr (TAG, "Failed to allocate program\n");
1119       goto delete_exit;
1120     }
1121
1122     status = comm_.extractGenericBuffer (model_buf, hwmem_prog->getData(), nullptr,
1123         model->getMetadata()->getMetaSize(),
1124         model->getMetadata()->getProgramSize());
1125     if (status != 0) {
1126       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
1127       goto delete_exit;
1128     }
1129
1130     /** register this model to the driver */
1131     model_config_t config;
1132     config.version = model->getMetadata()->getVersion ();
1133     config.dbuf_fd = hwmem_prog->getDmabuf ();
1134     config.program_size = hwmem_prog->getSize ();
1135     config.program_offset_addr = 0;
1136
1137     /** for metadata extended section */
1138     config.metadata_dbuf_fd = model->getDmabuf ();
1139     config.metadata_extra_addr = NPUBIN_META_SIZE;
1140     config.metadata_extra_size = model->getMetadata()->getMetaExtendedSize ();
1141
1142     status = api_->registerModel (&config, model->getMetadata()->getNPUVersion());
1143     if (status != 0)
1144       goto delete_exit;
1145
1146     model->setInternalID(config.id);
1147   }
1148
1149   /** allocate weight (optional) */
1150   if (model->getMetadata()->getWeightSize() > 0) {
1151     HWmem * hwmem_weight = new HWmem (new HWmemDevice);
1152     hwmem_weight->setDriverAPI (api_.get());
1153
1154     model->setWeightData (hwmem_weight);
1155
1156     status = hwmem_weight->alloc (model->getMetadata()->getWeightSize());
1157     if (status != 0) {
1158       logerr (TAG, "Failed to allocate program\n");
1159       goto delete_exit;
1160     }
1161
1162     status = comm_.extractGenericBuffer (model_buf, hwmem_weight->getData(), nullptr,
1163         model->getMetadata()->getMetaSize() + model->getMetadata()->getProgramSize(),
1164         model->getMetadata()->getWeightSize());
1165     if (status != 0) {
1166       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
1167       goto delete_exit;
1168     }
1169   }
1170
1171   *model_ptr = model;
1172   return status;
1173
1174 delete_exit:
1175   delete model;
1176   return status;
1177 }
1178
1179 /**
1180  * @brief implementation of TRIV2's unsetModel ()
1181  * @param[in] model the model instance
1182  * @return 0 if no error, otherwise a negative errno
1183  */
1184 int
1185 TrinityVision2::unsetModel (Model * model)
1186 {
1187   if (!initialized ()) {
1188     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1189     return -EPERM;
1190   }
1191
1192   if (model == nullptr) {
1193     logerr (TAG, "Invalid model instance\n");
1194     return -EINVAL;
1195   }
1196
1197   if (model->getMetadata()->getProgramSize() > 0)
1198     return api_->deregisterModel (model->getInternalID ());
1199
1200   return 0;
1201 }
1202
1203 /** @brief implementation of TRIV2's run() */
1204 int
1205 TrinityVision2::run (npu_input_opmode opmode, const Model *model,
1206     const input_buffers *input, npuOutputNotify cb, void *cb_data,
1207     uint64_t *sequence)
1208 {
1209   if (!initialized ()) {
1210     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1211     return -EPERM;
1212   }
1213
1214   if (opmode != NPUINPUT_HOST)
1215     return -EINVAL;
1216
1217   if (input == nullptr || input->num_buffers == 0 || model == nullptr)
1218     return -EINVAL;
1219
1220   const_cast<Model *>(model)->updateDataInfo ();
1221
1222   /** this device uses segment table */
1223   SegmentTable * segt = prepareSegmentTable (model, input);
1224   if (segt == nullptr) {
1225     logerr (TAG, "Failed to create segment table instance\n");
1226     return -EINVAL;
1227   }
1228
1229   /** extract input data */
1230   for (uint32_t idx = 0; idx < input->num_buffers; idx++) {
1231     if (!segt->getInputSegment(idx)->isExternal ()) {
1232       uint32_t seg_offset = segt->getInputSegmentOffset(idx);
1233       auto func = std::bind (TrinityVision2::manipulateData, model, idx, true,
1234           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
1235       int status = comm_.extractGenericBuffer (
1236           &input->bufs[idx],
1237           segt->getInputSegment(idx)->getData() + seg_offset,
1238           func);
1239       if (status != 0) {
1240         logerr (TAG, "Failed to feed input segment: %d\n", status);
1241         return status;
1242       }
1243     }
1244   }
1245
1246   Request *req = new Request (opmode);
1247   req->setModel (model);
1248   req->setSegmentTable (segt);
1249   req->setCallback (std::bind (&TrinityVision2::callback, this, req, cb, cb_data));
1250
1251   if (sequence)
1252     *sequence = req->getID();
1253
1254   return scheduler_->submitRequest (req);
1255 }
1256
1257 /** @brief implementation of TRIV2's runInternal() */
1258 int
1259 TrinityVision2::runInternal (npu_input_opmode opmode, const Model *model,
1260     std::string hw_dev)
1261 {
1262   if (!initialized ()) {
1263     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1264     return -EPERM;
1265   }
1266
1267   if (opmode != NPUINPUT_HW_RECURRING)
1268     return -EINVAL;
1269
1270   /** this device uses segment table */
1271   SegmentTable * segt = prepareSegmentTable (model, nullptr, nullptr);
1272   if (segt == nullptr) {
1273     logerr (TAG, "Failed to create segment table instance\n");
1274     return -EINVAL;
1275   }
1276
1277   Request *req = new Request (opmode);
1278   req->setModel (model);
1279   req->setSegmentTable (segt);
1280   req->setHwDevice (hw_dev);
1281
1282   return scheduler_->submitRequest (req);
1283 }
1284
1285 /** @brief callback of TRIV2 request */
1286 void
1287 TrinityVision2::callback (Request *req, npuOutputNotify cb, void *cb_data)
1288 {
1289   const Model *model = req->getModel ();
1290   SegmentTable *segt = req->getSegmentTable ();
1291   output_buffers output = {
1292     .num_buffers = segt->getNumOutputSegments ()
1293   };
1294
1295   for (uint32_t idx = 0; idx < output.num_buffers; idx++) {
1296     uint32_t output_tensor_size = model->getOutputTensorSize (idx);
1297
1298     output.bufs[idx].type = BUFFER_MAPPED;
1299     output.bufs[idx].size = output_tensor_size;
1300     /** user needs to free this */
1301     output.bufs[idx].addr = calloc (1, output_tensor_size);
1302
1303 #if defined(ENABLE_FPGA_WORKAROUND)
1304     api_->fpga_memcpy (
1305         segt->getOutputSegment(idx)->getDmabuf(),
1306         segt->getOutputSegmentOffset(idx),
1307         output.bufs[idx].addr,
1308         output.bufs[idx].size);
1309 #else
1310     auto func = std::bind (TrinityVision2::manipulateData, model, idx, false,
1311         std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
1312     int status = comm_.insertGenericBuffer (
1313         segt->getOutputSegment(idx)->getData() + segt->getOutputSegmentOffset(idx),
1314         &output.bufs[idx], func);
1315
1316     if (status != 0) {
1317       logerr (TAG, "Failed to return output buffer: %d\n", status);
1318     }
1319 #endif
1320   }
1321
1322   cb (&output, req->getID(), cb_data);
1323
1324   delete segt;
1325 }
1326
1327 /** @brief implementation of TRIA's run(): WIP */
1328 int
1329 TrinityAsr::run (npu_input_opmode opmode, const Model *model,
1330     const input_buffers *input, npuOutputNotify cb, void *cb_data,
1331     uint64_t *sequence)
1332 {
1333   if (!initialized ()) {
1334     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1335     return -EPERM;
1336   }
1337
1338   if (opmode != NPUINPUT_HOST)
1339     return -EINVAL;
1340
1341   if (input == nullptr || input->num_buffers != 1)
1342     return -EINVAL;
1343
1344   Buffer * buffer;
1345   int status;
1346   /** ASR does not require model and support only a single tensor */
1347   const generic_buffer *first_buf = &input->bufs[0];
1348   if (first_buf->type == BUFFER_DMABUF) {
1349     buffer = mem_->allocBuffer (new HWmemExternal);
1350     if (buffer == nullptr)
1351       return -ENOMEM;
1352
1353     buffer->setDmabuf (first_buf->dmabuf);
1354     buffer->setOffset (first_buf->offset);
1355     buffer->setSize (first_buf->size);
1356   } else {
1357     buffer = mem_->allocBuffer (new HWmemDevice);
1358     if (buffer == nullptr)
1359       return -ENOMEM;
1360
1361     status = buffer->alloc (first_buf->size);
1362     if (status != 0) {
1363       delete buffer;
1364       return status;
1365     }
1366   }
1367
1368   status = buffer->createTensors ();
1369   if (status != 0) {
1370     logerr (TAG, "Failed to create tensors: %d\n", status);
1371     delete buffer;
1372     return status;
1373   }
1374
1375   if (!buffer->isExternal ()) {
1376     status = comm_.extractGenericBuffer (first_buf,
1377         buffer->getInputTensor(0)->getData(), nullptr);
1378     if (status != 0)
1379       return status;
1380   }
1381
1382   Request *req = new Request (opmode);
1383   req->setBuffer (buffer);
1384   req->setCallback (std::bind (&TrinityAsr::callback, this, req, cb, cb_data));
1385
1386   if (sequence)
1387     *sequence = req->getID();
1388
1389   return scheduler_->submitRequest (req);
1390 }
1391
1392 /** @brief callback of TRIA request: WIP */
1393 void
1394 TrinityAsr::callback (Request *req, npuOutputNotify cb, void *cb_data)
1395 {
1396   Buffer *buffer = req->getBuffer ();
1397   output_buffers output = {
1398     .num_buffers = 0
1399   };
1400
1401   /** TODO: finalize this impl. when the ASR's working scenario is determined */
1402   cb (&output, req->getID(), cb_data);
1403
1404   delete buffer;
1405 }
1406
1407 /** Implement data manipulation (each device may have different impl.) */
1408
1409 #ifdef ENABLE_MANIP
1410
1411 /**
1412  * @brief perform data manipulation
1413  * @param[in] model model instance
1414  * @param[in] idx tensor index
1415  * @param[in] is_input indicate it's input manipulation
1416  * @param[out] dst destination buffer
1417  * @param[in] src source buffer (feature map)
1418  * @param[in] size size to be copied
1419  * @return size of memory copy if no error, otherwise zero
1420  *
1421  * @note the input data format should be NHWC
1422  * @detail rules for the memory address of activations in NPU HW.
1423  *         (https://code.sec.samsung.net/confluence/pages/viewpage.action?pageId=146491864)
1424  *
1425  * 1) Special case (depth == 3)
1426  * - addr(x,y,z) = addr(0,0,0) + (z) + 3 * (x + width * y)
1427  *
1428  * 2) Common case
1429  * - addr(x,y,z) = addr(0,0,0) + (z % MPA_L) + MPA_L * (x + width * (y + height * (z / MPA_L)))
1430  *
1431  * Thus, if depth is not a multiple of MPA_L (i.e., 64), zero padding is required
1432  */
1433 size_t
1434 TrinityVision::manipulateData (const Model *model, uint32_t idx, bool is_input,
1435     void *dst, void *src, size_t size)
1436 {
1437   const Metadata *meta = model->getMetadata();
1438   DataConverter converter (is_input);
1439
1440   converter.setData (src, dst, size);
1441
1442   if (is_input) {
1443     const tensor_data_info* info = model->getInputDataInfo (idx);
1444     if (info == nullptr)
1445       return 0;
1446
1447     converter.setDataLayout (info->layout, DATA_LAYOUT_SRNPU);
1448     converter.setDataType (info->type, DATA_TYPE_SRNPU);
1449     converter.setDataDims (meta->getInputDims (idx));
1450     converter.setQuantZero (meta->getInputQuantZero (idx));
1451     converter.setQuantScale (meta->getInputQuantScale (idx));
1452   } else {
1453     const tensor_data_info* info = model->getOutputDataInfo (idx);
1454     if (info == nullptr)
1455       return 0;
1456
1457     converter.setDataLayout (DATA_LAYOUT_SRNPU, info->layout);
1458     converter.setDataType (DATA_TYPE_SRNPU, info->type);
1459     converter.setDataDims (meta->getOutputDims (idx));
1460     converter.setQuantZero (meta->getOutputQuantZero (idx));
1461     converter.setQuantScale (meta->getOutputQuantScale (idx));
1462   }
1463
1464   return converter.perform ();
1465 }
1466
1467 /**
1468  * @brief perform data manipulation
1469  * @param[in] model model instance
1470  * @param[in] idx tensor index
1471  * @param[in] is_input indicate it's input manipulation
1472  * @param[out] dst destination buffer
1473  * @param[in] src source buffer (feature map)
1474  * @param[in] size size to be copied
1475  * @return size of memory copy if no error, otherwise zero
1476  *
1477  * @note the input data format should be NHWC
1478  *
1479  * @detail Feature map data in TRIV2, (x, y, z) = (width, height, depth)
1480  *
1481  *         1) Image input (depth == 1 or depth == 3)
1482  *            Addr(x,y,z) = Addr(0,0,0) + z + depth * x + ymod * y
1483  *
1484  *         2) Common cases
1485  *            Addr(x,y,z) = Addr(0,0,0) + (z % 64) + (64 * x) + ymod * y + zmod * (z / 64)
1486  */
1487 size_t
1488 TrinityVision2::manipulateData (const Model *model, uint32_t idx, bool is_input,
1489     void *dst, void *src, size_t size)
1490 {
1491   const Metadata *meta = model->getMetadata ();
1492   DataConverter converter (is_input);
1493
1494   converter.setData (src, dst, size);
1495   converter.setTops (meta->getTops ());
1496   if (is_input) {
1497     const tensor_data_info* info = model->getInputDataInfo (idx);
1498     if (info == nullptr)
1499       return 0;
1500
1501     converter.setDataLayout (info->layout, DATA_LAYOUT_TRIV2);
1502     converter.setDataType (info->type, meta->getInputQuantType (idx));
1503     converter.setDataDims (meta->getInputDims (idx));
1504     converter.setQuantZero (meta->getInputQuantZero (idx));
1505     converter.setQuantScale (meta->getInputQuantScale (idx));
1506   } else {
1507     const tensor_data_info* info = model->getOutputDataInfo (idx);
1508     if (info == nullptr)
1509       return 0;
1510
1511     converter.setDataLayout (DATA_LAYOUT_TRIV2, info->layout);
1512     converter.setDataType (meta->getOutputQuantType (idx), info->type);
1513     converter.setDataDims (meta->getOutputDims (idx));
1514     converter.setQuantZero (meta->getOutputQuantZero (idx));
1515     converter.setQuantScale (meta->getOutputQuantScale (idx));
1516   }
1517
1518   return converter.perform ();
1519 }
1520
1521 #else
1522
1523 size_t
1524 TrinityVision::manipulateData (const Model *model, uint32_t idx, bool is_input,
1525     void *dst, void *src, size_t size)
1526 {
1527   memcpy (dst, src, size);
1528   return size;
1529 }
1530
1531 size_t
1532 TrinityVision2::manipulateData (const Model *model, uint32_t idx, bool is_input,
1533     void *dst, void *src, size_t size)
1534 {
1535   memcpy (dst, src, size);
1536   return size;
1537 }
1538
1539 #endif
1540
1541 /** other device types don't have data manip impl. yet */
1542
1543 size_t
1544 TrinityAsr::manipulateData (const Model *model, uint32_t idx, bool is_input,
1545     void *dst, void *src, size_t size)
1546 {
1547   memcpy (dst, src, size);
1548   return size;
1549 }