1 /* Copyright (C) 2021-2024 Free Software Foundation, Inc.
2 Contributed by Oracle.
3
4 This file is part of GNU Binutils.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, 51 Franklin Street - Fifth Floor, Boston,
19 MA 02110-1301, USA. */
20
21 /* Hardware counter profiling */
22 #include "hwcdrv.h"
23 #include "hwcfuncs.h"
24
25 /*---------------------------------------------------------------------------*/
26 /* macros */
27
28 #define IS_GLOBAL /* Mark global symbols */
29 #define HWCDRV_API static /* Mark functions used by hwcdrv API */
30
31 /*---------------------------------------------------------------------------*/
32 /* static variables */
33 static uint_t cpcN_npics;
34 static char hwcfuncs_errmsg_buf[1024];
35 static int hwcfuncs_errmsg_enabled = 1;
36 static int hwcfuncs_errmsg_valid;
37
38 /* --- user counter selections and options */
39 static unsigned hwcdef_cnt; /* number of *active* hardware counters */
40 static Hwcentry hwcdef[MAX_PICS]; /* HWC definitions */
41 static Hwcentry *hwctable[MAX_PICS]; /* HWC definitions */
42
43 /* --- drivers --- */
44
45 // default driver
46
47 HWCDRV_API int
hwcdrv_init(hwcfuncs_abort_fn_t abort_ftn,int * tsd_sz)48 hwcdrv_init (hwcfuncs_abort_fn_t abort_ftn, int* tsd_sz)
49 {
50 return -1;
51 }
52
53 HWCDRV_API void
hwcdrv_get_info(int * cpuver,const char ** cciname,uint_t * npics,const char ** docref,uint64_t * support)54 hwcdrv_get_info (
55 int * cpuver, const char ** cciname,
56 uint_t * npics, const char ** docref, uint64_t* support) { }
57
58 HWCDRV_API int
hwcdrv_enable_mt(hwcfuncs_tsd_get_fn_t tsd_ftn)59 hwcdrv_enable_mt (hwcfuncs_tsd_get_fn_t tsd_ftn)
60 {
61 return -1;
62 }
63
64 HWCDRV_API int
hwcdrv_get_descriptions(hwcf_hwc_cb_t * hwc_find_action,hwcf_attr_cb_t * attr_find_action)65 hwcdrv_get_descriptions (hwcf_hwc_cb_t *hwc_find_action,
66 hwcf_attr_cb_t *attr_find_action)
67 {
68 return 0;
69 }
70
71 HWCDRV_API int
hwcdrv_assign_regnos(Hwcentry * entries[],unsigned numctrs)72 hwcdrv_assign_regnos (Hwcentry *entries[], unsigned numctrs)
73 {
74 return -1;
75 }
76
77 HWCDRV_API int
hwcdrv_create_counters(unsigned hwcdef_cnt,Hwcentry * hwcdef)78 hwcdrv_create_counters (unsigned hwcdef_cnt, Hwcentry *hwcdef)
79 {
80 return -1;
81 }
82
83 HWCDRV_API int
hwcdrv_read_events(hwc_event_t * events,hwc_event_samples_t * samples)84 hwcdrv_read_events (hwc_event_t *events, hwc_event_samples_t*samples)
85 {
86 return -1;
87 }
88
89 HWCDRV_API int
hwcdrv_start(void)90 hwcdrv_start (void)
91 {
92 return -1;
93 }
94
95 HWCDRV_API int
hwcdrv_overflow(siginfo_t * si,hwc_event_t * s,hwc_event_t * t)96 hwcdrv_overflow (siginfo_t *si, hwc_event_t *s, hwc_event_t *t)
97 {
98 return 0;
99 }
100
101 HWCDRV_API int
hwcdrv_sighlr_restart(const hwc_event_t * sample)102 hwcdrv_sighlr_restart (const hwc_event_t *sample)
103 {
104 return -1;
105 }
106
107 HWCDRV_API int
hwcdrv_lwp_suspend(void)108 hwcdrv_lwp_suspend (void)
109 {
110 return -1;
111 }
112
113 HWCDRV_API int
hwcdrv_lwp_resume(void)114 hwcdrv_lwp_resume (void)
115 {
116 return -1;
117 }
118
119 HWCDRV_API int
hwcdrv_free_counters(void)120 hwcdrv_free_counters (void)
121 {
122 return 0;
123 }
124
125 HWCDRV_API int
hwcdrv_lwp_init(void)126 hwcdrv_lwp_init (void)
127 {
128 return 0;
129 }
130
131 HWCDRV_API void
hwcdrv_lwp_fini(void)132 hwcdrv_lwp_fini (void) { }
133
134 static hwcdrv_api_t hwcdrv_default = {
135 hwcdrv_init,
136 hwcdrv_get_info,
137 hwcdrv_enable_mt,
138 hwcdrv_get_descriptions,
139 hwcdrv_assign_regnos,
140 hwcdrv_create_counters,
141 hwcdrv_start,
142 hwcdrv_overflow,
143 hwcdrv_read_events,
144 hwcdrv_sighlr_restart,
145 hwcdrv_lwp_suspend,
146 hwcdrv_lwp_resume,
147 hwcdrv_free_counters,
148 hwcdrv_lwp_init,
149 hwcdrv_lwp_fini,
150 -1 // hwcdrv_init_status
151 };
152
153 static hwcdrv_api_t *hwcdrv_driver = &hwcdrv_default;
154
155
156 /*---------------------------------------------------------------------------*/
157 /* misc */
158
159 /* print a counter definition (for debugging) */
160 static void
ctrdefprint(int dbg_lvl,const char * hdr,Hwcentry * phwcdef)161 ctrdefprint (int dbg_lvl, const char * hdr, Hwcentry*phwcdef)
162 {
163 TprintfT (dbg_lvl, "%s: name='%s', int_name='%s',"
164 " reg_num=%d, timecvt=%d, memop=%d, "
165 "interval=%d, tag=%u, reg_list=%p\n",
166 hdr, phwcdef->name, phwcdef->int_name, phwcdef->reg_num,
167 phwcdef->timecvt, phwcdef->memop, phwcdef->val,
168 phwcdef->sort_order, phwcdef->reg_list);
169 }
170
171 /*---------------------------------------------------------------------------*/
172 /* errmsg buffering */
173
174 /* errmsg buffering is needed only because the most descriptive error
175 messages from CPC are delivered using a callback mechanism.
176 hwcfuncs_errmsg_get() should only be used during initialization, and
177 ideally, only to provide feedback to an end user when his counters can't
178 be bound to HW.
179 */
180 IS_GLOBAL char *
hwcfuncs_errmsg_get(char * buf,size_t bufsize,int enable)181 hwcfuncs_errmsg_get (char *buf, size_t bufsize, int enable)
182 {
183 hwcfuncs_errmsg_enabled = 0;
184 if (buf && bufsize)
185 {
186 if (hwcfuncs_errmsg_valid)
187 {
188 strncpy (buf, hwcfuncs_errmsg_buf, bufsize);
189 buf[bufsize - 1] = 0;
190 }
191 else
192 *buf = 0;
193 }
194 hwcfuncs_errmsg_buf[0] = 0;
195 hwcfuncs_errmsg_valid = 0;
196 hwcfuncs_errmsg_enabled = enable;
197 return buf;
198 }
199
200 /* used by cpc to log an error */
201 IS_GLOBAL void
hwcfuncs_int_capture_errmsg(const char * fn,int subcode,const char * fmt,va_list ap)202 hwcfuncs_int_capture_errmsg (const char *fn, int subcode,
203 const char *fmt, va_list ap)
204 {
205 if (hwcfuncs_errmsg_enabled &&
206 !hwcfuncs_errmsg_valid)
207 {
208 vsnprintf (hwcfuncs_errmsg_buf, sizeof (hwcfuncs_errmsg_buf), fmt, ap);
209 TprintfT (DBG_LT0, "hwcfuncs: cpcN_capture_errmsg(): %s\n",
210 hwcfuncs_errmsg_buf);
211 hwcfuncs_errmsg_valid = 1;
212 }
213 return;
214 }
215
216 /* Log an internal error to the CPC error buffer.
217 * Note: only call this during init functions.
218 * Note: when most cpc calls fail, they will call cpcN_capture_errmsg()
219 * directly, so only call logerr() when a non-cpc function fails.
220 */
221 IS_GLOBAL void
hwcfuncs_int_logerr(const char * format,...)222 hwcfuncs_int_logerr (const char *format, ...)
223 {
224 va_list va;
225 va_start (va, format);
226 hwcfuncs_int_capture_errmsg ("logerr", 0, format, va);
227 va_end (va);
228 }
229
230 /* utils to parse counter strings */
231 static void
clear_hwcdefs()232 clear_hwcdefs ()
233 {
234 for (unsigned idx = 0; idx < MAX_PICS; idx++)
235 {
236 static Hwcentry empty;
237 hwcdef[idx] = empty; // leaks strings and reg_list array
238 hwcdef[idx].reg_num = REGNO_ANY;
239 hwcdef[idx].val = -1;
240 hwcdef[idx].sort_order = -1;
241 }
242 }
243
244 /* initialize hwcdef[] based on user's counter definitions */
245 static int
process_data_descriptor(const char * defstring)246 process_data_descriptor (const char *defstring)
247 {
248 /*
249 * <defstring> format should be of format
250 * :%s:%s:0x%x:%d:%lld:%d:%d:0x%x[,%s...repeat for each ctr]
251 * where the counter fields are:
252 * :<userName>:<internalCtr>:<register>:<timeoutVal>[:m<min_time>]:<tag>:<timecvt>:<memop>
253 * See Coll_Ctrl::build_data_desc().
254 */
255 int err = 0;
256 char *ds = NULL;
257 char *dsp = NULL;
258 unsigned idx;
259
260 clear_hwcdefs ();
261 if (!defstring || !strlen (defstring))
262 return HWCFUNCS_ERROR_HWCARGS;
263 ds = strdup (defstring);
264 if (!ds)
265 return HWCFUNCS_ERROR_HWCINIT;
266 dsp = ds;
267 for (idx = 0; idx < MAX_PICS && *dsp; idx++)
268 {
269 char *name = NULL;
270 char *int_name = NULL;
271 regno_t reg = REGNO_ANY;
272 ABST_type memop = ABST_NONE;
273 int interval = 0;
274 int timecvt = 0;
275 unsigned sort_order = (unsigned) - 1;
276
277 // Read use_perf_event_type, type, config
278 hwcdef[idx].use_perf_event_type = (int) strtol (dsp, &dsp, 0);
279 if (*dsp++ != ':')
280 {
281 err = HWCFUNCS_ERROR_HWCARGS;
282 break;
283 }
284 hwcdef[idx].type = (int) strtol (dsp, &dsp, 0);
285 if (*dsp++ != ':')
286 {
287 err = HWCFUNCS_ERROR_HWCARGS;
288 break;
289 }
290 hwcdef[idx].config = strtol (dsp, &dsp, 0);
291 if (*dsp++ != ':')
292 {
293 err = HWCFUNCS_ERROR_HWCARGS;
294 break;
295 }
296
297 /* name */
298 name = dsp;
299 dsp = strchr (dsp, ':');
300 if (dsp == NULL)
301 {
302 err = HWCFUNCS_ERROR_HWCARGS;
303 break;
304 }
305 *dsp++ = (char) 0;
306
307 /* int_name */
308 int_name = dsp;
309 dsp = strchr (dsp, ':');
310 if (dsp == NULL)
311 {
312 err = HWCFUNCS_ERROR_HWCARGS;
313 break;
314 }
315 *dsp++ = (char) 0;
316
317 /* reg_num */
318 reg = (int) strtol (dsp, &dsp, 0);
319 if (*dsp++ != ':')
320 {
321 err = HWCFUNCS_ERROR_HWCARGS;
322 break;
323 }
324 if (reg < 0 && reg != -1)
325 {
326 err = HWCFUNCS_ERROR_HWCARGS;
327 break;
328 }
329 if (reg >= 0)
330 hwcdef[idx].reg_num = reg;
331
332 /* val */
333 interval = (int) strtol (dsp, &dsp, 0);
334 if (*dsp++ != ':')
335 {
336 err = HWCFUNCS_ERROR_HWCARGS;
337 break;
338 }
339 if (interval < 0)
340 {
341 err = HWCFUNCS_ERROR_HWCARGS;
342 break;
343 }
344 hwcdef[idx].val = interval;
345
346 /* min_time */
347 if (*dsp == 'm')
348 {
349 long long tmp_ll = 0;
350 dsp++;
351 tmp_ll = strtoll (dsp, &dsp, 0);
352 if (*dsp++ != ':')
353 {
354 err = HWCFUNCS_ERROR_HWCARGS;
355 break;
356 }
357 if (tmp_ll < 0)
358 {
359 err = HWCFUNCS_ERROR_HWCARGS;
360 break;
361 }
362 hwcdef[idx].min_time = tmp_ll;
363 }
364 else
365 hwcdef[idx].min_time = 0;
366
367 /* sort_order */
368 sort_order = (int) strtoul (dsp, &dsp, 0);
369 if (*dsp++ != ':')
370 {
371 err = HWCFUNCS_ERROR_HWCARGS;
372 break;
373 }
374 hwcdef[idx].sort_order = sort_order;
375
376 /* timecvt */
377 timecvt = (int) strtol (dsp, &dsp, 0);
378 if (*dsp++ != ':')
379 {
380 err = HWCFUNCS_ERROR_HWCARGS;
381 break;
382 }
383 hwcdef[idx].timecvt = timecvt;
384
385 /* memop */
386 memop = (ABST_type) strtol (dsp, &dsp, 0);
387 if (*dsp != 0 && *dsp++ != ',')
388 {
389 err = HWCFUNCS_ERROR_HWCARGS;
390 break;
391 }
392 hwcdef[idx].memop = memop;
393 if (*name)
394 hwcdef[idx].name = strdup (name);
395 else
396 hwcdef[idx].name = strdup (int_name);
397 if (*int_name)
398 hwcdef[idx].int_name = strdup (int_name);
399 else
400 hwcdef[idx].int_name = strdup (name);
401 ctrdefprint (DBG_LT1, "hwcfuncs: process_data_descriptor", &hwcdef[idx]);
402 }
403
404 if (*dsp)
405 err = HWCFUNCS_ERROR_HWCARGS;
406 if (err != 0)
407 logerr (GTXT ("Data descriptor syntax error near `%s'\n"), dsp);
408 else
409 hwcdef_cnt = idx;
410 free (ds);
411 return err;
412 }
413
414 /* initialize hwcdef[] based on user's counter definitions */
415 static int
process_hwcentrylist(const Hwcentry * entries[],unsigned numctrs)416 process_hwcentrylist (const Hwcentry* entries[], unsigned numctrs)
417 {
418 int err = 0;
419 clear_hwcdefs ();
420 if (numctrs > cpcN_npics)
421 {
422 logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
423 return HWCFUNCS_ERROR_HWCARGS;
424 }
425 for (unsigned idx = 0; idx < numctrs; idx++)
426 {
427 Hwcentry *phwcdef = &hwcdef[idx];
428 *phwcdef = *entries[idx];
429 if (phwcdef->name)
430 phwcdef->name = strdup (phwcdef->name);
431 else
432 phwcdef->name = "NULL";
433 if (phwcdef->int_name)
434 phwcdef->int_name = strdup (phwcdef->int_name);
435 else
436 phwcdef->int_name = "NULL";
437 if (phwcdef->val < 0)
438 {
439 logerr (GTXT ("Negative interval specified for HW counter `%s'\n"), /*!*/
440 phwcdef->name);
441 err = HWCFUNCS_ERROR_HWCARGS;
442 break;
443 }
444 ctrdefprint (DBG_LT1, "hwcfuncs: process_hwcentrylist", phwcdef);
445 }
446 if (!err)
447 hwcdef_cnt = numctrs;
448 return err;
449 }
450
451 /* see hwcfuncs.h */
452 IS_GLOBAL void *
hwcfuncs_parse_attrs(const char * countername,hwcfuncs_attr_t attrs[],unsigned max_attrs,uint_t * pnum_attrs,char ** errstring)453 hwcfuncs_parse_attrs (const char *countername, hwcfuncs_attr_t attrs[],
454 unsigned max_attrs, uint_t *pnum_attrs, char**errstring)
455 {
456 char *head = NULL;
457 char *tail = NULL;
458 uint_t nattrs = 0;
459 char *counter_copy;
460 int success = 0;
461 char errbuf[512];
462 errbuf[0] = 0;
463 counter_copy = strdup (countername);
464
465 /* advance pointer to first attribute */
466 tail = strchr (counter_copy, HWCFUNCS_PARSE_ATTR);
467 if (tail)
468 *tail = 0;
469
470 /* remove regno and value, if supplied */
471 {
472 char *tmp = strchr (counter_copy, HWCFUNCS_PARSE_REGNUM);
473 if (tmp)
474 *tmp = 0;
475 tmp = strchr (counter_copy, HWCFUNCS_PARSE_VALUE);
476 if (tmp)
477 *tmp = 0;
478 }
479
480 while (tail)
481 {
482 char *pch;
483 if (nattrs >= max_attrs)
484 {
485 snprintf (errbuf, sizeof (errbuf),
486 GTXT ("Too many attributes defined in `%s'"),
487 countername);
488 goto mycpc2_parse_attrs_end;
489 }
490 /* get attribute name */
491 head = tail + 1;
492 tail = strchr (head, HWCFUNCS_PARSE_EQUAL);
493 if (!tail)
494 {
495 snprintf (errbuf, sizeof (errbuf),
496 GTXT ("Missing value for attribute `%s' in `%s'"),
497 head, countername);
498 goto mycpc2_parse_attrs_end;
499 }
500 *tail = 0; /* null terminate current component */
501 attrs[nattrs].ca_name = head;
502
503 /* get attribute value */
504 head = tail + 1;
505 tail = strchr (head, HWCFUNCS_PARSE_ATTR);
506 if (tail)
507 *tail = 0; /* null terminate current component */
508 attrs[nattrs].ca_val = strtoull (head, &pch, 0);
509 if (pch == head)
510 {
511 snprintf (errbuf, sizeof (errbuf),
512 GTXT ("Illegal value for attribute `%s' in `%s'"),
513 attrs[nattrs].ca_name, countername);
514 goto mycpc2_parse_attrs_end;
515 }
516 TprintfT (DBG_LT0, "hwcfuncs: pic_: '%s', attribute[%u]"
517 " '%s' = 0x%llx\n",
518 counter_copy, nattrs, attrs[nattrs].ca_name,
519 (long long unsigned int) attrs[nattrs].ca_val);
520
521 nattrs++;
522 }
523 success = 1;
524
525 mycpc2_parse_attrs_end:
526 *pnum_attrs = nattrs;
527 if (success)
528 {
529 if (errstring)
530 *errstring = NULL;
531 }
532 else
533 {
534 if (errstring)
535 *errstring = strdup (errbuf);
536 free (counter_copy);
537 counter_copy = NULL;
538 }
539 return counter_copy;
540 }
541
542 IS_GLOBAL void
hwcfuncs_parse_ctr(const char * counter_def,int * pplus,char ** pnameOnly,char ** pattrs,char ** pregstr,regno_t * pregno)543 hwcfuncs_parse_ctr (const char *counter_def, int *pplus, char **pnameOnly,
544 char **pattrs, char **pregstr, regno_t *pregno)
545 {
546 char *nameptr, *copy, *slash, *attr_delim;
547 int plus;
548 regno_t regno;
549 nameptr = copy = strdup (counter_def);
550
551 /* plus */
552 plus = 0;
553 if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK)
554 {
555 plus = 1;
556 nameptr++;
557 }
558 else if (nameptr[0] == HWCFUNCS_PARSE_BACKTRACK_OFF)
559 {
560 plus = -1;
561 nameptr++;
562 }
563 if (pplus)
564 *pplus = plus;
565
566 /* regno */
567 regno = REGNO_ANY;
568 if (pregstr)
569 *pregstr = NULL;
570 slash = strchr (nameptr, HWCFUNCS_PARSE_REGNUM);
571 if (slash != NULL)
572 {
573 /* the remaining string should be a number > 0 */
574 if (pregstr)
575 *pregstr = strdup (slash);
576 char *endchar = NULL;
577 regno = (regno_t) strtol (slash + 1, &endchar, 0);
578 if (*endchar != 0)
579 regno = -2;
580 if (*(slash + 1) == '-')
581 regno = -2;
582 /* terminate previous element up to slash */
583 *slash = 0;
584 }
585 if (pregno)
586 *pregno = regno;
587
588 /* attrs */
589 if (pattrs)
590 *pattrs = NULL;
591 attr_delim = strchr (nameptr, HWCFUNCS_PARSE_ATTR);
592 if (attr_delim != NULL)
593 {
594 if (pattrs)
595 *pattrs = strdup (attr_delim);
596 /* terminate previous element up to attr_delim */
597 *attr_delim++ = 0;
598 }
599 if (pnameOnly)
600 *pnameOnly = strdup (nameptr);
601 free (copy);
602 }
603
604 /* create counters */
605 IS_GLOBAL int
hwcfuncs_bind_descriptor(const char * defstring)606 hwcfuncs_bind_descriptor (const char *defstring)
607 {
608 int err = process_data_descriptor (defstring);
609 if (err)
610 {
611 TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_descriptor failed\n");
612 return err;
613 }
614 err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
615 return err;
616 }
617
618 /* see hwcfuncs.h */
619 IS_GLOBAL int
hwcfuncs_bind_hwcentry(const Hwcentry * entries[],unsigned numctrs)620 hwcfuncs_bind_hwcentry (const Hwcentry* entries[], unsigned numctrs)
621 {
622 int err = -1;
623 err = process_hwcentrylist (entries, numctrs);
624 if (err)
625 {
626 TprintfT (DBG_LT0, "hwcfuncs: ERROR: hwcfuncs_bind_hwcentry\n");
627 return err;
628 }
629 err = hwcdrv_driver->hwcdrv_create_counters (hwcdef_cnt, hwcdef);
630 return err;
631 }
632
633 /* see hwcfuncs.h */
634 IS_GLOBAL Hwcentry **
hwcfuncs_get_ctrs(unsigned * defcnt)635 hwcfuncs_get_ctrs (unsigned *defcnt)
636 {
637 if (defcnt)
638 *defcnt = hwcdef_cnt;
639 return hwctable;
640 }
641
642 /* return 1 if <regno> is in Hwcentry's list */
643 IS_GLOBAL int
regno_is_valid(const Hwcentry * pctr,regno_t regno)644 regno_is_valid (const Hwcentry * pctr, regno_t regno)
645 {
646 regno_t *reg_list = pctr->reg_list;
647 if (REG_LIST_IS_EMPTY (reg_list))
648 return 0;
649 if (regno == REGNO_ANY) /* wildcard */
650 return 1;
651 for (int ii = 0; ii < MAX_PICS; ii++)
652 {
653 regno_t tmp = reg_list[ii];
654 if (REG_LIST_EOL (tmp)) /* end of list */
655 break;
656 if (tmp == regno) /* is in list */
657 return 1;
658 }
659 return 0;
660 }
661
662 /* supplied by hwcdrv_api drivers */
663 IS_GLOBAL int
hwcfuncs_assign_regnos(Hwcentry * entries[],unsigned numctrs)664 hwcfuncs_assign_regnos (Hwcentry* entries[],
665 unsigned numctrs)
666 {
667 if (numctrs > cpcN_npics)
668 {
669 logerr (GTXT ("More than %d counters were specified\n"), cpcN_npics); /*!*/
670 return HWCFUNCS_ERROR_HWCARGS;
671 }
672 return hwcdrv_driver->hwcdrv_assign_regnos (entries, numctrs);
673 }
674
675 extern hwcdrv_api_t hwcdrv_pcl_api;
676 static int hwcdrv_driver_inited = 0;
677
678 hwcdrv_api_t *
get_hwcdrv()679 get_hwcdrv ()
680 {
681 if (hwcdrv_driver_inited)
682 return hwcdrv_driver;
683 hwcdrv_driver_inited = 1;
684 cpcN_npics = 0;
685 for (int i = 0; i < MAX_PICS; i++)
686 hwctable[i] = &hwcdef[i];
687 hwcdrv_driver = &hwcdrv_pcl_api;
688 hwcdrv_driver->hwcdrv_init_status = hwcdrv_driver->hwcdrv_init (NULL, NULL);
689 if (hwcdrv_driver->hwcdrv_init_status == 0)
690 {
691 hwcdrv_driver->hwcdrv_get_info (NULL, NULL, &cpcN_npics, NULL, NULL);
692 return hwcdrv_driver;
693 }
694 hwcdrv_driver = &hwcdrv_default;
695 return hwcdrv_driver;
696 }
697