blob: edd6184ff63d0d97a98febf6ea740bc83e659c3d [file] [log] [blame]
Eric Laurent135ad072010-05-21 06:05:13 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "EffectsFactory"
18//#define LOG_NDEBUG 0
19
20#include "EffectsFactory.h"
Eric Laurent17217ab2010-05-25 12:38:34 -070021#include <string.h>
22#include <stdlib.h>
Eric Laurent135ad072010-05-21 06:05:13 -070023#include <dlfcn.h>
24
25
26static list_elem_t *gEffectList; // list of effect_entry_t: all currently created effects
27static list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries
28static pthread_mutex_t gLibLock = PTHREAD_MUTEX_INITIALIZER; // controls access to gLibraryList
Eric Laurentffe9c252010-06-23 17:38:20 -070029static uint32_t gNumEffects; // total number number of effects
Eric Laurent135ad072010-05-21 06:05:13 -070030static list_elem_t *gCurLib; // current library in enumeration process
31static list_elem_t *gCurEffect; // current effect in enumeration process
Eric Laurentffe9c252010-06-23 17:38:20 -070032static uint32_t gCurEffectIdx; // current effect index in enumeration process
Eric Laurent135ad072010-05-21 06:05:13 -070033
34static const char * const gEffectLibPath = "/system/lib/soundfx"; // path to built-in effect libraries
35static int gInitDone; // true is global initialization has been preformed
Eric Laurentffe9c252010-06-23 17:38:20 -070036static int gNextLibId; // used by loadLibrary() to allocate unique library handles
37static int gCanQueryEffect; // indicates that call to EffectQueryEffect() is valid, i.e. that the list of effects
38 // was not modified since last call to EffectQueryNumberEffects()
Eric Laurent135ad072010-05-21 06:05:13 -070039
40/////////////////////////////////////////////////
41// Local functions prototypes
42/////////////////////////////////////////////////
43
44static int init();
45static int loadLibrary(const char *libPath, int *handle);
46static int unloadLibrary(int handle);
Eric Laurentffe9c252010-06-23 17:38:20 -070047static void resetEffectEnumeration();
48static uint32_t updateNumEffects();
Eric Laurent135ad072010-05-21 06:05:13 -070049static int findEffect(effect_uuid_t *uuid, lib_entry_t **lib, effect_descriptor_t **desc);
50static void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len);
51
52/////////////////////////////////////////////////
53// Effect Control Interface functions
54/////////////////////////////////////////////////
55
56int Effect_Process(effect_interface_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer)
57{
58 int ret = init();
59 if (ret < 0) {
60 return ret;
61 }
62 effect_entry_t *fx = (effect_entry_t *)self;
63 pthread_mutex_lock(&gLibLock);
64 if (fx->lib == NULL) {
65 pthread_mutex_unlock(&gLibLock);
66 return -EPIPE;
67 }
68 pthread_mutex_lock(&fx->lib->lock);
69 pthread_mutex_unlock(&gLibLock);
70
71 ret = (*fx->subItfe)->process(fx->subItfe, inBuffer, outBuffer);
72 pthread_mutex_unlock(&fx->lib->lock);
73 return ret;
74}
75
76int Effect_Command(effect_interface_t self, int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData)
77{
78 int ret = init();
79 if (ret < 0) {
80 return ret;
81 }
82 effect_entry_t *fx = (effect_entry_t *)self;
83 pthread_mutex_lock(&gLibLock);
84 if (fx->lib == NULL) {
85 pthread_mutex_unlock(&gLibLock);
86 return -EPIPE;
87 }
88 pthread_mutex_lock(&fx->lib->lock);
89 pthread_mutex_unlock(&gLibLock);
90
91 ret = (*fx->subItfe)->command(fx->subItfe, cmdCode, cmdSize, pCmdData, replySize, pReplyData);
92 pthread_mutex_unlock(&fx->lib->lock);
93 return ret;
94}
95
96const struct effect_interface_s gInterface = {
97 Effect_Process,
98 Effect_Command
99};
100
101/////////////////////////////////////////////////
102// Effect Factory Interface functions
103/////////////////////////////////////////////////
104
Eric Laurentbe916aa2010-06-01 23:49:17 -0700105int EffectQueryNumberEffects(uint32_t *pNumEffects)
Eric Laurent135ad072010-05-21 06:05:13 -0700106{
107 int ret = init();
108 if (ret < 0) {
109 return ret;
110 }
111 if (pNumEffects == NULL) {
112 return -EINVAL;
113 }
114
115 pthread_mutex_lock(&gLibLock);
Eric Laurentffe9c252010-06-23 17:38:20 -0700116 *pNumEffects = gNumEffects;
117 gCanQueryEffect = 1;
Eric Laurent135ad072010-05-21 06:05:13 -0700118 pthread_mutex_unlock(&gLibLock);
119 LOGV("EffectQueryNumberEffects(): %d", *pNumEffects);
120 return ret;
121}
122
Eric Laurentffe9c252010-06-23 17:38:20 -0700123int EffectQueryEffect(uint32_t index, effect_descriptor_t *pDescriptor)
Eric Laurent135ad072010-05-21 06:05:13 -0700124{
125 int ret = init();
126 if (ret < 0) {
127 return ret;
128 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700129 if (pDescriptor == NULL ||
130 index >= gNumEffects) {
Eric Laurent135ad072010-05-21 06:05:13 -0700131 return -EINVAL;
132 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700133 if (gCanQueryEffect == 0) {
134 return -ENOSYS;
135 }
Eric Laurent135ad072010-05-21 06:05:13 -0700136
137 pthread_mutex_lock(&gLibLock);
138 ret = -ENOENT;
Eric Laurentffe9c252010-06-23 17:38:20 -0700139 if (index < gCurEffectIdx) {
140 resetEffectEnumeration();
141 }
Eric Laurent135ad072010-05-21 06:05:13 -0700142 while (gCurLib) {
143 if (gCurEffect) {
Eric Laurentffe9c252010-06-23 17:38:20 -0700144 if (index == gCurEffectIdx) {
145 memcpy(pDescriptor, gCurEffect->object, sizeof(effect_descriptor_t));
146 ret = 0;
147 break;
148 } else {
149 gCurEffect = gCurEffect->next;
150 gCurEffectIdx++;
151 }
Eric Laurent135ad072010-05-21 06:05:13 -0700152 } else {
153 gCurLib = gCurLib->next;
154 gCurEffect = ((lib_entry_t *)gCurLib->object)->effects;
155 }
156 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700157
158#if (LOG_NDEBUG == 0)
Eric Laurent135ad072010-05-21 06:05:13 -0700159 char str[256];
160 dumpEffectDescriptor(pDescriptor, str, 256);
Eric Laurentffe9c252010-06-23 17:38:20 -0700161 LOGV("EffectQueryEffect() desc:%s", str);
162#endif
Eric Laurent135ad072010-05-21 06:05:13 -0700163 pthread_mutex_unlock(&gLibLock);
164 return ret;
165}
166
167int EffectGetDescriptor(effect_uuid_t *uuid, effect_descriptor_t *pDescriptor)
168{
169 lib_entry_t *l = NULL;
170 effect_descriptor_t *d = NULL;
171
172 int ret = init();
173 if (ret < 0) {
174 return ret;
175 }
176 if (pDescriptor == NULL || uuid == NULL) {
177 return -EINVAL;
178 }
179 pthread_mutex_lock(&gLibLock);
180 ret = findEffect(uuid, &l, &d);
181 if (ret == 0) {
182 memcpy(pDescriptor, d, sizeof(effect_descriptor_t));
183 }
184 pthread_mutex_unlock(&gLibLock);
185 return ret;
186}
187
Eric Laurentffe9c252010-06-23 17:38:20 -0700188int EffectCreate(effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, effect_interface_t *pInterface)
Eric Laurent135ad072010-05-21 06:05:13 -0700189{
190 list_elem_t *e = gLibraryList;
191 lib_entry_t *l = NULL;
192 effect_descriptor_t *d = NULL;
193 effect_interface_t itfe;
194 effect_entry_t *fx;
195 int found = 0;
196 int ret;
197
198 if (uuid == NULL || pInterface == NULL) {
199 return -EINVAL;
200 }
201
202 LOGV("EffectCreate() UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
203 uuid->timeLow, uuid->timeMid, uuid->timeHiAndVersion,
204 uuid->clockSeq, uuid->node[0], uuid->node[1],uuid->node[2],
205 uuid->node[3],uuid->node[4],uuid->node[5]);
206
207 ret = init();
208
209 if (ret < 0) {
210 LOGW("EffectCreate() init error: %d", ret);
211 return ret;
212 }
213
214 pthread_mutex_lock(&gLibLock);
215
216 ret = findEffect(uuid, &l, &d);
217 if (ret < 0){
218 goto exit;
219 }
220
221 // create effect in library
Eric Laurentffe9c252010-06-23 17:38:20 -0700222 ret = l->createFx(uuid, sessionId, ioId, &itfe);
223 if (ret != 0) {
224 LOGW("EffectCreate() library %s: could not create fx %s, error %d", l->path, d->name, ret);
Eric Laurent135ad072010-05-21 06:05:13 -0700225 goto exit;
226 }
227
228 // add entry to effect list
229 fx = (effect_entry_t *)malloc(sizeof(effect_entry_t));
230 fx->subItfe = itfe;
231 fx->itfe = (struct effect_interface_s *)&gInterface;
232 fx->lib = l;
233
234 e = (list_elem_t *)malloc(sizeof(list_elem_t));
235 e->object = fx;
236 e->next = gEffectList;
237 gEffectList = e;
238
239 *pInterface = (effect_interface_t)fx;
240
241 LOGV("EffectCreate() created entry %p with sub itfe %p in library %s", *pInterface, itfe, l->path);
242
243exit:
244 pthread_mutex_unlock(&gLibLock);
245 return ret;
246}
247
248int EffectRelease(effect_interface_t interface)
249{
250 effect_entry_t *fx;
251 list_elem_t *e1;
252 list_elem_t *e2;
253
254 int ret = init();
255 if (ret < 0) {
256 return ret;
257 }
258
259 // remove effect from effect list
260 pthread_mutex_lock(&gLibLock);
261 e1 = gEffectList;
262 e2 = NULL;
263 while (e1) {
264 if (e1->object == interface) {
265 if (e2) {
266 e2->next = e1->next;
267 } else {
268 gEffectList = e1->next;
269 }
270 fx = (effect_entry_t *)e1->object;
271 free(e1);
272 break;
273 }
274 e2 = e1;
275 e1 = e1->next;
276 }
277 if (e1 == NULL) {
278 ret = -ENOENT;
279 goto exit;
280 }
281
282 // release effect in library
283 if (fx->lib == NULL) {
284 LOGW("EffectRelease() fx %p library already unloaded", interface);
285 } else {
286 pthread_mutex_lock(&fx->lib->lock);
287 fx->lib->releaseFx(fx->subItfe);
288 pthread_mutex_unlock(&fx->lib->lock);
289 }
290 free(fx);
291
292exit:
293 pthread_mutex_unlock(&gLibLock);
294 return ret;
295}
296
297int EffectLoadLibrary(const char *libPath, int *handle)
298{
299 int ret = init();
300 if (ret < 0) {
301 return ret;
302 }
Eric Laurent17217ab2010-05-25 12:38:34 -0700303 if (libPath == NULL) {
Eric Laurent135ad072010-05-21 06:05:13 -0700304 return -EINVAL;
305 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700306
307 ret = loadLibrary(libPath, handle);
308 updateNumEffects();
309 return ret;
Eric Laurent135ad072010-05-21 06:05:13 -0700310}
311
312int EffectUnloadLibrary(int handle)
313{
314 int ret = init();
315 if (ret < 0) {
316 return ret;
317 }
318
Eric Laurentffe9c252010-06-23 17:38:20 -0700319 ret = unloadLibrary(handle);
320 updateNumEffects();
321 return ret;
Eric Laurent135ad072010-05-21 06:05:13 -0700322}
323
324int EffectIsNullUuid(effect_uuid_t *uuid)
325{
326 if (memcmp(uuid, EFFECT_UUID_NULL, sizeof(effect_uuid_t))) {
327 return 0;
328 }
329 return 1;
330}
331
332/////////////////////////////////////////////////
333// Local functions
334/////////////////////////////////////////////////
335
336int init() {
337 struct dirent *ent;
338 DIR *dir = NULL;
339 char libpath[PATH_MAX];
340 int hdl;
341
342 if (gInitDone) {
343 return 0;
344 }
345
346 pthread_mutex_init(&gLibLock, NULL);
347
348 // load built-in libraries
349 dir = opendir(gEffectLibPath);
350 if (dir == NULL) {
351 return -ENODEV;
352 }
353 while ((ent = readdir(dir)) != NULL) {
354 LOGV("init() reading file %s", ent->d_name);
355 if ((strlen(ent->d_name) < 3) ||
356 strncmp(ent->d_name, "lib", 3) != 0 ||
357 strncmp(ent->d_name + strlen(ent->d_name) - 3, ".so", 3) != 0) {
358 continue;
359 }
360 strcpy(libpath, gEffectLibPath);
361 strcat(libpath, "/");
362 strcat(libpath, ent->d_name);
363 if (loadLibrary(libpath, &hdl) < 0) {
364 LOGW("init() failed to load library %s",libpath);
365 }
366 }
367 closedir(dir);
Eric Laurentffe9c252010-06-23 17:38:20 -0700368 updateNumEffects();
Eric Laurent135ad072010-05-21 06:05:13 -0700369 gInitDone = 1;
370 LOGV("init() done");
371 return 0;
372}
373
374
375int loadLibrary(const char *libPath, int *handle)
376{
377 void *hdl;
378 effect_QueryNumberEffects_t queryNumFx;
Eric Laurentffe9c252010-06-23 17:38:20 -0700379 effect_QueryEffect_t queryFx;
Eric Laurent135ad072010-05-21 06:05:13 -0700380 effect_CreateEffect_t createFx;
381 effect_ReleaseEffect_t releaseFx;
Eric Laurentbe916aa2010-06-01 23:49:17 -0700382 uint32_t numFx;
383 uint32_t fx;
Eric Laurent135ad072010-05-21 06:05:13 -0700384 int ret;
385 list_elem_t *e, *descHead = NULL;
386 lib_entry_t *l;
387
388 if (handle == NULL) {
389 return -EINVAL;
390 }
391
392 *handle = 0;
393
394 hdl = dlopen(libPath, RTLD_NOW);
395 if (hdl == 0) {
396 LOGW("could open lib %s", libPath);
397 return -ENODEV;
398 }
399
400 // Check functions availability
401 queryNumFx = (effect_QueryNumberEffects_t)dlsym(hdl, "EffectQueryNumberEffects");
402 if (queryNumFx == NULL) {
403 LOGW("could not get EffectQueryNumberEffects from lib %s", libPath);
404 ret = -ENODEV;
405 goto error;
406 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700407 queryFx = (effect_QueryEffect_t)dlsym(hdl, "EffectQueryEffect");
Eric Laurent135ad072010-05-21 06:05:13 -0700408 if (queryFx == NULL) {
Eric Laurentffe9c252010-06-23 17:38:20 -0700409 LOGW("could not get EffectQueryEffect from lib %s", libPath);
Eric Laurent135ad072010-05-21 06:05:13 -0700410 ret = -ENODEV;
411 goto error;
412 }
413 createFx = (effect_CreateEffect_t)dlsym(hdl, "EffectCreate");
414 if (createFx == NULL) {
415 LOGW("could not get EffectCreate from lib %s", libPath);
416 ret = -ENODEV;
417 goto error;
418 }
419 releaseFx = (effect_ReleaseEffect_t)dlsym(hdl, "EffectRelease");
420 if (releaseFx == NULL) {
421 LOGW("could not get EffectRelease from lib %s", libPath);
422 ret = -ENODEV;
423 goto error;
424 }
425
426 // load effect descriptors
427 ret = queryNumFx(&numFx);
428 if (ret) {
429 goto error;
430 }
431
432 for (fx = 0; fx < numFx; fx++) {
433 effect_descriptor_t *d = malloc(sizeof(effect_descriptor_t));
434 if (d == NULL) {
435 ret = -ENOMEM;
436 goto error;
437 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700438 ret = queryFx(fx, d);
Eric Laurent135ad072010-05-21 06:05:13 -0700439 if (ret == 0) {
440#if (LOG_NDEBUG==0)
441 char s[256];
442 dumpEffectDescriptor(d, s, 256);
443 LOGV("loadLibrary() read descriptor %p:%s",d, s);
444#endif
445 if (d->apiVersion != EFFECT_API_VERSION) {
446 LOGW("Bad API version %04x on lib %s", d->apiVersion, libPath);
447 free(d);
448 continue;
449 }
450 e = malloc(sizeof(list_elem_t));
451 if (e == NULL) {
452 free(d);
453 ret = -ENOMEM;
454 goto error;
455 }
456 e->object = d;
457 e->next = descHead;
458 descHead = e;
459 } else {
460 LOGW("Error querying effect # %d on lib %s", fx, libPath);
461 }
462 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700463
464 pthread_mutex_lock(&gLibLock);
465
Eric Laurent135ad072010-05-21 06:05:13 -0700466 // add entry for library in gLibraryList
467 l = malloc(sizeof(lib_entry_t));
Eric Laurentffe9c252010-06-23 17:38:20 -0700468 l->id = ++gNextLibId;
Eric Laurent135ad072010-05-21 06:05:13 -0700469 l->handle = hdl;
470 strncpy(l->path, libPath, PATH_MAX);
471 l->createFx = createFx;
472 l->releaseFx = releaseFx;
473 l->effects = descHead;
474 pthread_mutex_init(&l->lock, NULL);
475
476 e = malloc(sizeof(list_elem_t));
Eric Laurent135ad072010-05-21 06:05:13 -0700477 e->next = gLibraryList;
478 e->object = l;
479 gLibraryList = e;
480 pthread_mutex_unlock(&gLibLock);
481 LOGV("loadLibrary() linked library %p", l);
482
Eric Laurentffe9c252010-06-23 17:38:20 -0700483 *handle = l->id;
Eric Laurent135ad072010-05-21 06:05:13 -0700484
485 return 0;
486
487error:
488 LOGW("loadLibrary() error: %d on lib: %s", ret, libPath);
489 while (descHead) {
490 free(descHead->object);
491 e = descHead->next;
492 free(descHead);
493 descHead = e;;
494 }
495 dlclose(hdl);
496 return ret;
497}
498
499int unloadLibrary(int handle)
500{
501 void *hdl;
502 int ret;
503 list_elem_t *el1, *el2;
504 lib_entry_t *l;
505 effect_entry_t *fx;
506
507 pthread_mutex_lock(&gLibLock);
508 el1 = gLibraryList;
509 el2 = NULL;
510 while (el1) {
511 l = (lib_entry_t *)el1->object;
Eric Laurentffe9c252010-06-23 17:38:20 -0700512 if (handle == l->id) {
Eric Laurent135ad072010-05-21 06:05:13 -0700513 if (el2) {
514 el2->next = el1->next;
515 } else {
516 gLibraryList = el1->next;
517 }
518 free(el1);
519 break;
520 }
521 el2 = el1;
522 el1 = el1->next;
523 }
524 pthread_mutex_unlock(&gLibLock);
525 if (el1 == NULL) {
526 return -ENOENT;
527 }
528
529 // clear effect descriptor list
530 el1 = l->effects;
531 while (el1) {
532 free(el1->object);
533 el2 = el1->next;
534 free(el1);
535 el1 = el2;
536 }
537
538 // disable all effects from this library
539 pthread_mutex_lock(&l->lock);
Eric Laurentffe9c252010-06-23 17:38:20 -0700540
Eric Laurent135ad072010-05-21 06:05:13 -0700541 el1 = gEffectList;
542 while (el1) {
543 fx = (effect_entry_t *)el1->object;
544 if (fx->lib == l) {
545 fx->lib = NULL;
546 }
547 el1 = el1->next;
548 }
549 pthread_mutex_unlock(&l->lock);
550
551 dlclose(l->handle);
552 free(l);
553 return 0;
554}
555
Eric Laurentffe9c252010-06-23 17:38:20 -0700556void resetEffectEnumeration()
557{
558 gCurLib = gLibraryList;
559 gCurEffect = NULL;
560 if (gCurLib) {
561 gCurEffect = ((lib_entry_t *)gCurLib->object)->effects;
562 }
563 gCurEffectIdx = 0;
564}
Eric Laurent135ad072010-05-21 06:05:13 -0700565
Eric Laurentffe9c252010-06-23 17:38:20 -0700566uint32_t updateNumEffects() {
567 list_elem_t *e;
Eric Laurentbe916aa2010-06-01 23:49:17 -0700568 uint32_t cnt = 0;
Eric Laurent135ad072010-05-21 06:05:13 -0700569
Eric Laurentffe9c252010-06-23 17:38:20 -0700570 resetEffectEnumeration();
571
572 e = gLibraryList;
Eric Laurent135ad072010-05-21 06:05:13 -0700573 while (e) {
574 lib_entry_t *l = (lib_entry_t *)e->object;
575 list_elem_t *efx = l->effects;
576 while (efx) {
577 cnt++;
578 efx = efx->next;
579 }
580 e = e->next;
581 }
Eric Laurentffe9c252010-06-23 17:38:20 -0700582 gNumEffects = cnt;
583 gCanQueryEffect = 0;
Eric Laurent135ad072010-05-21 06:05:13 -0700584 return cnt;
585}
586
587int findEffect(effect_uuid_t *uuid, lib_entry_t **lib, effect_descriptor_t **desc)
588{
589 list_elem_t *e = gLibraryList;
590 lib_entry_t *l = NULL;
591 effect_descriptor_t *d = NULL;
592 int found = 0;
593 int ret = 0;
594
595 while (e && !found) {
596 l = (lib_entry_t *)e->object;
597 list_elem_t *efx = l->effects;
598 while (efx) {
599 d = (effect_descriptor_t *)efx->object;
600 if (memcmp(&d->uuid, uuid, sizeof(effect_uuid_t)) == 0) {
601 found = 1;
602 break;
603 }
604 efx = efx->next;
605 }
606 e = e->next;
607 }
608 if (!found) {
609 LOGV("findEffect() effect not found");
610 ret = -ENOENT;
611 } else {
612 LOGV("findEffect() found effect: %s in lib %s", d->name, l->path);
613 *lib = l;
614 *desc = d;
615 }
616
617 return ret;
618}
619
620void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len) {
621 char s[256];
622
623 snprintf(str, len, "\nEffect Descriptor %p:\n", desc);
624 sprintf(s, "- UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
625 desc->uuid.timeLow, desc->uuid.timeMid, desc->uuid.timeHiAndVersion,
626 desc->uuid.clockSeq, desc->uuid.node[0], desc->uuid.node[1],desc->uuid.node[2],
627 desc->uuid.node[3],desc->uuid.node[4],desc->uuid.node[5]);
628 strncat(str, s, len);
629 sprintf(s, "- TYPE: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
630 desc->type.timeLow, desc->type.timeMid, desc->type.timeHiAndVersion,
631 desc->type.clockSeq, desc->type.node[0], desc->type.node[1],desc->type.node[2],
632 desc->type.node[3],desc->type.node[4],desc->type.node[5]);
633 strncat(str, s, len);
634 sprintf(s, "- apiVersion: %04X\n- flags: %08X\n",
635 desc->apiVersion, desc->flags);
636 strncat(str, s, len);
637 sprintf(s, "- name: %s\n", desc->name);
638 strncat(str, s, len);
639 sprintf(s, "- implementor: %s\n", desc->implementor);
640 strncat(str, s, len);
641}
642