[Profile] Implement the profile interface and fix minor bugs
[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.dbuf_fd = hwmem_prog->getDmabuf ();
866     config.program_size = hwmem_prog->getSize ();
867     config.program_offset_addr = hwmem_prog->getOffset ();
868     if (hwmem_weight != nullptr)
869       config.weight_offset_addr = hwmem_weight->getOffset ();
870
871     status = api_->registerModel (&config);
872     if (status != 0)
873       goto delete_exit;
874
875     model->setInternalID(config.id);
876   }
877
878   *model_ptr = model;
879   return status;
880
881 delete_exit:
882   delete model;
883   return status;
884 }
885
886 /**
887  * @brief implementation of TRIV's unsetModel ()
888  * @param[in] model the model instance
889  * @return 0 if no error, otherwise a negative errno
890  */
891 int
892 TrinityVision::unsetModel (Model * model)
893 {
894   if (!initialized ()) {
895     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
896     return -EPERM;
897   }
898
899   if (model == nullptr) {
900     logerr (TAG, "Invalid model instance\n");
901     return -EINVAL;
902   }
903
904   if (model->getMetadata()->getProgramSize() > 0)
905     return api_->deregisterModel (model->getInternalID ());
906
907   return 0;
908 }
909
910 /**
911  * @brief implementation of TRIV's run()
912  * @param[in] opmode input opmode
913  * @param[in] model the model instance
914  * @param[in] input generic buffers of input data
915  * @param[in] cb the output callback
916  * @param[in] cb_data the output callback data
917  * @param[out] sequence The sequence number returned with runNPU_async.
918  */
919 int
920 TrinityVision::run (npu_input_opmode opmode, const Model *model,
921     const input_buffers *input, npuOutputNotify cb, void *cb_data,
922     uint64_t *sequence)
923 {
924   if (!initialized ()) {
925     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
926     return -EPERM;
927   }
928
929   if (opmode != NPUINPUT_HOST) {
930     logerr (TAG, "TRIV supports only host inputservice\n");
931     return -EINVAL;
932   }
933
934   if (model == nullptr || input == nullptr) {
935     logerr (TAG, "TRIV requires both model and input buffers\n");
936     return -EINVAL;
937   }
938
939   const_cast<Model *>(model)->updateDataInfo ();
940
941   Buffer *buffer = prepareInputBuffers (model->getMetadata(), input);
942   if (buffer == nullptr) {
943     logerr (TAG, "Failed to extract buffer instance\n");
944     return -EINVAL;
945   }
946
947   if (!buffer->isExternal ()) {
948     for (uint32_t idx = 0; idx < input->num_buffers; idx++) {
949       auto func = std::bind (TrinityVision::manipulateData, model, idx, true,
950           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
951       int status = comm_.extractGenericBuffer (&input->bufs[idx],
952           buffer->getInputTensor(idx)->getData(), func);
953       if (status != 0) {
954         logerr (TAG, "Failed to feed input buffer: %d\n", status);
955         return status;
956       }
957     }
958   }
959
960   /** this device uses CMA buffer */
961
962   Request *req = new Request (opmode);
963   req->setModel (model);
964   req->setBuffer (buffer);
965
966   if (cb != nullptr)
967     req->setCallback (std::bind (&TrinityVision::callback, this, req, cb, cb_data));
968
969   if (sequence != nullptr)
970     *sequence = req->getID();
971
972   return scheduler_->submitRequest (req);
973 }
974
975 /**
976  * @brief callback of TRIV2 request
977  * @param[in] req the request instance
978  * @param[in] cb callback for completion
979  * @param[in] cb_data callback data
980  * @note The callback invoke does not gurantee the request was successful
981  * @todo Check the request failures
982  */
983 void
984 TrinityVision::callback (Request *req, npuOutputNotify cb, void *cb_data)
985 {
986   const Model *model = req->getModel ();
987   Buffer *buffer = req->getBuffer ();
988   output_buffers output = {
989     .num_buffers = buffer->getOutputNum ()
990   };
991
992   for (uint32_t idx = 0; idx < output.num_buffers; idx++) {
993     uint32_t output_tensor_size = model->getOutputTensorSize (idx);
994
995     if (buffer->isExternal ()) {
996       output.bufs[idx].type = BUFFER_DMABUF;
997       output.bufs[idx].size = output_tensor_size;
998       output.bufs[idx].addr = buffer->getOutputTensor(idx)->getData();
999     } else {
1000       output.bufs[idx].type = BUFFER_MAPPED;
1001       output.bufs[idx].size = output_tensor_size;
1002       /** user needs to free this */
1003       output.bufs[idx].addr = malloc (output_tensor_size);
1004
1005       auto func = std::bind (TrinityVision::manipulateData, model, idx, false,
1006           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
1007       int status = comm_.insertGenericBuffer (buffer->getOutputTensor(idx)->getData(),
1008           &output.bufs[idx], func);
1009       if (status != 0) {
1010         logerr (TAG, "Failed to return output buffer: %d\n", status);
1011       }
1012     }
1013   }
1014
1015   cb (&output, req->getID(), cb_data);
1016
1017   delete buffer;
1018 }
1019
1020 /**
1021  * @brief extract the segment table instance from input generic buffers
1022  * @param[in] model the model instance
1023  * @param[in] input the input generic buffers
1024  * @param[in] output the output generic buffers
1025  * @return the segment table instance
1026  */
1027 SegmentTable *
1028 TrinityVision2::prepareSegmentTable (const Model *model, const input_buffers *input,
1029     const output_buffers *output)
1030 {
1031   const Metadata *meta = model->getMetadata ();
1032   if (meta == nullptr || (input != nullptr &&
1033         meta->getInputNum() != input->num_buffers)) {
1034     logerr (TAG, "Invalid metadata info provided\n");
1035     return nullptr;
1036   }
1037
1038   SegmentTable * segt = mem_->allocSegmentTable (new HWmemDevice);
1039   int status = segt->alloc ();
1040   if (status != 0) {
1041     logerr (TAG, "Failed to allocate segment table: %d\n", status);
1042     goto delete_segt;
1043   }
1044
1045   status = segt->createSegments (model, input, output);
1046   if (status != 0) {
1047     logerr (TAG, "Failed to create segments: %d\n", status);
1048     goto delete_segt;
1049   }
1050
1051   return segt;
1052
1053 delete_segt:
1054   delete segt;
1055   return nullptr;
1056 }
1057
1058 /**
1059  * @brief implementation of TRIV2's setModel ()
1060  * @param[in] model_buf the model generic buffer
1061  * @param[out] model the model instance
1062  * @return 0 if no error, otherwise a negative errno
1063  */
1064 int
1065 TrinityVision2::setModel (const generic_buffer *model_buf, Model ** model_ptr)
1066 {
1067   if (!initialized ()) {
1068     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1069     return -EPERM;
1070   }
1071
1072   if (model_buf == nullptr || model_ptr == nullptr)
1073     return -EINVAL;
1074
1075   Model *model;
1076   int status;
1077
1078   switch (model_buf->type) {
1079   case BUFFER_FILE:
1080   case BUFFER_MAPPED:
1081     model = mem_->allocModel (new HWmemDevice);
1082     if (model == nullptr) {
1083       logerr (TAG, "Failed to allocate model\n");
1084       return -ENOMEM;
1085     }
1086
1087     status = model->alloc (NPUBIN_META_SIZE);
1088     if (status != 0) {
1089       logerr (TAG, "Failed to allocate model: %d\n", status);
1090       goto delete_exit;
1091     }
1092
1093     status = comm_.extractGenericBuffer (model_buf, model->getData(), nullptr,
1094         0, NPUBIN_META_SIZE);
1095     if (status != 0) {
1096       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
1097       goto delete_exit;
1098     }
1099     break;
1100   default:
1101     return -EINVAL;
1102   }
1103
1104   status = model->setMetadata (model->getData());
1105   if (status != 0)
1106     goto delete_exit;
1107
1108   /** allocate program (optional; NOP) */
1109   if (model->getMetadata()->getProgramSize() > 0) {
1110     HWmem * hwmem_prog = new HWmem (new HWmemDevice);
1111     hwmem_prog->setDriverAPI (api_.get());
1112
1113     model->setProgramData (hwmem_prog);
1114
1115     status = hwmem_prog->alloc (model->getMetadata()->getProgramSize());
1116     if (status != 0) {
1117       logerr (TAG, "Failed to allocate program\n");
1118       goto delete_exit;
1119     }
1120
1121     status = comm_.extractGenericBuffer (model_buf, hwmem_prog->getData(), nullptr,
1122         model->getMetadata()->getMetaSize(),
1123         model->getMetadata()->getProgramSize());
1124     if (status != 0) {
1125       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
1126       goto delete_exit;
1127     }
1128
1129     /** register this model to the driver */
1130     model_config_t config;
1131     config.dbuf_fd = hwmem_prog->getDmabuf ();
1132     config.program_size = hwmem_prog->getSize ();
1133     config.program_offset_addr = 0;
1134
1135     /** for metadata extra section */
1136     config.metadata_dbuf_fd = model->getDmabuf ();
1137     config.metadata_extra_addr = NPUBIN_META_SIZE;
1138     config.metadata_extra_size = model->getMetadata()->getMetaExtraSize ();
1139
1140     status = api_->registerModel (&config, model->getMetadata()->getNPUVersion());
1141     if (status != 0)
1142       goto delete_exit;
1143
1144     model->setInternalID(config.id);
1145   }
1146
1147   /** allocate weight (optional) */
1148   if (model->getMetadata()->getWeightSize() > 0) {
1149     HWmem * hwmem_weight = new HWmem (new HWmemDevice);
1150     hwmem_weight->setDriverAPI (api_.get());
1151
1152     model->setWeightData (hwmem_weight);
1153
1154     status = hwmem_weight->alloc (model->getMetadata()->getWeightSize());
1155     if (status != 0) {
1156       logerr (TAG, "Failed to allocate program\n");
1157       goto delete_exit;
1158     }
1159
1160     status = comm_.extractGenericBuffer (model_buf, hwmem_weight->getData(), nullptr,
1161         model->getMetadata()->getMetaSize() + model->getMetadata()->getProgramSize(),
1162         model->getMetadata()->getWeightSize());
1163     if (status != 0) {
1164       logerr (TAG, "Failed to extract generic buffer: %d\n", status);
1165       goto delete_exit;
1166     }
1167   }
1168
1169   *model_ptr = model;
1170   return status;
1171
1172 delete_exit:
1173   delete model;
1174   return status;
1175 }
1176
1177 /**
1178  * @brief implementation of TRIV2's unsetModel ()
1179  * @param[in] model the model instance
1180  * @return 0 if no error, otherwise a negative errno
1181  */
1182 int
1183 TrinityVision2::unsetModel (Model * model)
1184 {
1185   if (!initialized ()) {
1186     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1187     return -EPERM;
1188   }
1189
1190   if (model == nullptr) {
1191     logerr (TAG, "Invalid model instance\n");
1192     return -EINVAL;
1193   }
1194
1195   if (model->getMetadata()->getProgramSize() > 0)
1196     return api_->deregisterModel (model->getInternalID ());
1197
1198   return 0;
1199 }
1200
1201 /** @brief implementation of TRIV2's run() */
1202 int
1203 TrinityVision2::run (npu_input_opmode opmode, const Model *model,
1204     const input_buffers *input, npuOutputNotify cb, void *cb_data,
1205     uint64_t *sequence)
1206 {
1207   if (!initialized ()) {
1208     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1209     return -EPERM;
1210   }
1211
1212   if (opmode != NPUINPUT_HOST)
1213     return -EINVAL;
1214
1215   if (input == nullptr || input->num_buffers == 0 || model == nullptr)
1216     return -EINVAL;
1217
1218   const_cast<Model *>(model)->updateDataInfo ();
1219
1220   /** this device uses segment table */
1221   SegmentTable * segt = prepareSegmentTable (model, input);
1222   if (segt == nullptr) {
1223     logerr (TAG, "Failed to create segment table instance\n");
1224     return -EINVAL;
1225   }
1226
1227   /** extract input data */
1228   for (uint32_t idx = 0; idx < input->num_buffers; idx++) {
1229     if (!segt->getInputSegment(idx)->isExternal ()) {
1230       uint32_t seg_offset = segt->getInputSegmentOffset(idx);
1231       auto func = std::bind (TrinityVision2::manipulateData, model, idx, true,
1232           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
1233       int status = comm_.extractGenericBuffer (
1234           &input->bufs[idx],
1235           segt->getInputSegment(idx)->getData() + seg_offset,
1236           func);
1237       if (status != 0) {
1238         logerr (TAG, "Failed to feed input segment: %d\n", status);
1239         return status;
1240       }
1241     }
1242   }
1243
1244   Request *req = new Request (opmode);
1245   req->setModel (model);
1246   req->setSegmentTable (segt);
1247   req->setCallback (std::bind (&TrinityVision2::callback, this, req, cb, cb_data));
1248
1249   if (sequence)
1250     *sequence = req->getID();
1251
1252   return scheduler_->submitRequest (req);
1253 }
1254
1255 /** @brief implementation of TRIV2's runInternal() */
1256 int
1257 TrinityVision2::runInternal (npu_input_opmode opmode, const Model *model,
1258     std::string hw_dev)
1259 {
1260   if (!initialized ()) {
1261     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1262     return -EPERM;
1263   }
1264
1265   if (opmode != NPUINPUT_HW_RECURRING)
1266     return -EINVAL;
1267
1268   /** this device uses segment table */
1269   SegmentTable * segt = prepareSegmentTable (model, nullptr, nullptr);
1270   if (segt == nullptr) {
1271     logerr (TAG, "Failed to create segment table instance\n");
1272     return -EINVAL;
1273   }
1274
1275   Request *req = new Request (opmode);
1276   req->setModel (model);
1277   req->setSegmentTable (segt);
1278   req->setHwDevice (hw_dev);
1279
1280   return scheduler_->submitRequest (req);
1281 }
1282
1283 /** @brief callback of TRIV2 request */
1284 void
1285 TrinityVision2::callback (Request *req, npuOutputNotify cb, void *cb_data)
1286 {
1287   const Model *model = req->getModel ();
1288   SegmentTable *segt = req->getSegmentTable ();
1289   output_buffers output = {
1290     .num_buffers = segt->getNumOutputSegments ()
1291   };
1292
1293   for (uint32_t idx = 0; idx < output.num_buffers; idx++) {
1294     uint32_t output_tensor_size = model->getOutputTensorSize (idx);
1295
1296     output.bufs[idx].type = BUFFER_MAPPED;
1297     output.bufs[idx].size = output_tensor_size;
1298     /** user needs to free this */
1299     output.bufs[idx].addr = calloc (1, output_tensor_size);
1300
1301 #if defined(ENABLE_FPGA_WORKAROUND)
1302     api_->fpga_memcpy (
1303         segt->getOutputSegment(idx)->getDmabuf(),
1304         segt->getOutputSegmentOffset(idx),
1305         output.bufs[idx].addr,
1306         output.bufs[idx].size);
1307 #else
1308     auto func = std::bind (TrinityVision2::manipulateData, model, idx, false,
1309         std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
1310     int status = comm_.insertGenericBuffer (
1311         segt->getOutputSegment(idx)->getData() + segt->getOutputSegmentOffset(idx),
1312         &output.bufs[idx], func);
1313
1314     if (status != 0) {
1315       logerr (TAG, "Failed to return output buffer: %d\n", status);
1316     }
1317 #endif
1318   }
1319
1320   cb (&output, req->getID(), cb_data);
1321
1322   delete segt;
1323 }
1324
1325 /** @brief implementation of TRIA's run(): WIP */
1326 int
1327 TrinityAsr::run (npu_input_opmode opmode, const Model *model,
1328     const input_buffers *input, npuOutputNotify cb, void *cb_data,
1329     uint64_t *sequence)
1330 {
1331   if (!initialized ()) {
1332     logerr (TAG, "Uninitialized device; should use libnpuhost APIs\n");
1333     return -EPERM;
1334   }
1335
1336   if (opmode != NPUINPUT_HOST)
1337     return -EINVAL;
1338
1339   if (input == nullptr || input->num_buffers != 1)
1340     return -EINVAL;
1341
1342   Buffer * buffer;
1343   int status;
1344   /** ASR does not require model and support only a single tensor */
1345   const generic_buffer *first_buf = &input->bufs[0];
1346   if (first_buf->type == BUFFER_DMABUF) {
1347     buffer = mem_->allocBuffer (new HWmemExternal);
1348     if (buffer == nullptr)
1349       return -ENOMEM;
1350
1351     buffer->setDmabuf (first_buf->dmabuf);
1352     buffer->setOffset (first_buf->offset);
1353     buffer->setSize (first_buf->size);
1354   } else {
1355     buffer = mem_->allocBuffer (new HWmemDevice);
1356     if (buffer == nullptr)
1357       return -ENOMEM;
1358
1359     status = buffer->alloc (first_buf->size);
1360     if (status != 0) {
1361       delete buffer;
1362       return status;
1363     }
1364   }
1365
1366   status = buffer->createTensors ();
1367   if (status != 0) {
1368     logerr (TAG, "Failed to create tensors: %d\n", status);
1369     delete buffer;
1370     return status;
1371   }
1372
1373   if (!buffer->isExternal ()) {
1374     status = comm_.extractGenericBuffer (first_buf,
1375         buffer->getInputTensor(0)->getData(), nullptr);
1376     if (status != 0)
1377       return status;
1378   }
1379
1380   Request *req = new Request (opmode);
1381   req->setBuffer (buffer);
1382   req->setCallback (std::bind (&TrinityAsr::callback, this, req, cb, cb_data));
1383
1384   if (sequence)
1385     *sequence = req->getID();
1386
1387   return scheduler_->submitRequest (req);
1388 }
1389
1390 /** @brief callback of TRIA request: WIP */
1391 void
1392 TrinityAsr::callback (Request *req, npuOutputNotify cb, void *cb_data)
1393 {
1394   Buffer *buffer = req->getBuffer ();
1395   output_buffers output = {
1396     .num_buffers = 0
1397   };
1398
1399   /** TODO: finalize this impl. when the ASR's working scenario is determined */
1400   cb (&output, req->getID(), cb_data);
1401
1402   delete buffer;
1403 }
1404
1405 /** Implement data manipulation (each device may have different impl.) */
1406
1407 #ifdef ENABLE_MANIP
1408
1409 /**
1410  * @brief perform data manipulation
1411  * @param[in] model model instance
1412  * @param[in] idx tensor index
1413  * @param[in] is_input indicate it's input manipulation
1414  * @param[out] dst destination buffer
1415  * @param[in] src source buffer (feature map)
1416  * @param[in] size size to be copied
1417  * @return size of memory copy if no error, otherwise zero
1418  *
1419  * @note the input data format should be NHWC
1420  * @detail rules for the memory address of activations in NPU HW.
1421  *         (https://code.sec.samsung.net/confluence/pages/viewpage.action?pageId=146491864)
1422  *
1423  * 1) Special case (depth == 3)
1424  * - addr(x,y,z) = addr(0,0,0) + (z) + 3 * (x + width * y)
1425  *
1426  * 2) Common case
1427  * - addr(x,y,z) = addr(0,0,0) + (z % MPA_L) + MPA_L * (x + width * (y + height * (z / MPA_L)))
1428  *
1429  * Thus, if depth is not a multiple of MPA_L (i.e., 64), zero padding is required
1430  */
1431 size_t
1432 TrinityVision::manipulateData (const Model *model, uint32_t idx, bool is_input,
1433     void *dst, void *src, size_t size)
1434 {
1435   const Metadata *meta = model->getMetadata();
1436   DataConverter converter (is_input);
1437
1438   converter.setData (src, dst, size);
1439
1440   if (is_input) {
1441     const tensor_data_info* info = model->getInputDataInfo (idx);
1442     if (info == nullptr)
1443       return 0;
1444
1445     converter.setDataLayout (info->layout, DATA_LAYOUT_SRNPU);
1446     converter.setDataType (info->type, DATA_TYPE_SRNPU);
1447     converter.setDataDims (meta->getInputDims (idx));
1448     converter.setQuantZero (meta->getInputQuantZero (idx));
1449     converter.setQuantScale (meta->getInputQuantScale (idx));
1450   } else {
1451     const tensor_data_info* info = model->getOutputDataInfo (idx);
1452     if (info == nullptr)
1453       return 0;
1454
1455     converter.setDataLayout (DATA_LAYOUT_SRNPU, info->layout);
1456     converter.setDataType (DATA_TYPE_SRNPU, info->type);
1457     converter.setDataDims (meta->getOutputDims (idx));
1458     converter.setQuantZero (meta->getOutputQuantZero (idx));
1459     converter.setQuantScale (meta->getOutputQuantScale (idx));
1460   }
1461
1462   return converter.perform ();
1463 }
1464
1465 /**
1466  * @brief perform data manipulation
1467  * @param[in] model model instance
1468  * @param[in] idx tensor index
1469  * @param[in] is_input indicate it's input manipulation
1470  * @param[out] dst destination buffer
1471  * @param[in] src source buffer (feature map)
1472  * @param[in] size size to be copied
1473  * @return size of memory copy if no error, otherwise zero
1474  *
1475  * @note the input data format should be NHWC
1476  *
1477  * @detail Feature map data in TRIV2, (x, y, z) = (width, height, depth)
1478  *
1479  *         1) Image input (depth == 1 or depth == 3)
1480  *            Addr(x,y,z) = Addr(0,0,0) + z + depth * x + ymod * y
1481  *
1482  *         2) Common cases
1483  *            Addr(x,y,z) = Addr(0,0,0) + (z % 64) + (64 * x) + ymod * y + zmod * (z / 64)
1484  */
1485 size_t
1486 TrinityVision2::manipulateData (const Model *model, uint32_t idx, bool is_input,
1487     void *dst, void *src, size_t size)
1488 {
1489   const Metadata *meta = model->getMetadata();
1490   DataConverter converter (is_input);
1491
1492   converter.setData (src, dst, size);
1493
1494   if (is_input) {
1495     const tensor_data_info* info = model->getInputDataInfo (idx);
1496     if (info == nullptr)
1497       return 0;
1498
1499     converter.setDataLayout (info->layout, DATA_LAYOUT_TRIV2);
1500     converter.setDataType (info->type, meta->getInputQuantType (idx));
1501     converter.setDataDims (meta->getInputDims (idx));
1502     converter.setQuantZero (meta->getInputQuantZero (idx));
1503     converter.setQuantScale (meta->getInputQuantScale (idx));
1504   } else {
1505     const tensor_data_info* info = model->getOutputDataInfo (idx);
1506     if (info == nullptr)
1507       return 0;
1508
1509     converter.setDataLayout (DATA_LAYOUT_TRIV2, info->layout);
1510     converter.setDataType (meta->getOutputQuantType (idx), info->type);
1511     converter.setDataDims (meta->getOutputDims (idx));
1512     converter.setQuantZero (meta->getOutputQuantZero (idx));
1513     converter.setQuantScale (meta->getOutputQuantScale (idx));
1514   }
1515
1516   return converter.perform ();
1517 }
1518
1519 #else
1520
1521 size_t
1522 TrinityVision::manipulateData (const Model *model, uint32_t idx, bool is_input,
1523     void *dst, void *src, size_t size)
1524 {
1525   memcpy (dst, src, size);
1526   return size;
1527 }
1528
1529 size_t
1530 TrinityVision2::manipulateData (const Model *model, uint32_t idx, bool is_input,
1531     void *dst, void *src, size_t size)
1532 {
1533   memcpy (dst, src, size);
1534   return size;
1535 }
1536
1537 #endif
1538
1539 /** other device types don't have data manip impl. yet */
1540
1541 size_t
1542 TrinityAsr::manipulateData (const Model *model, uint32_t idx, bool is_input,
1543     void *dst, void *src, size_t size)
1544 {
1545   memcpy (dst, src, size);
1546   return size;
1547 }