1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2009 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 
18 
19 // client-specific GPU code.  Mostly GPU detection
20 //
21 //  theory of operation:
22 //  there are two ways of detecting GPUs:
23 //  - vendor-specific libraries like CUDA and CAL,
24 //      which detect only that vendor's GPUs
25 //  - OpenCL, which can detect multiple types of GPUs,
26 //      including nvidia/amd/intel as well was new types
27 //      such as ARM integrated GPUs
28 //
29 //  These libraries sometimes crash,
30 //  and we've been unable to trap these via signal and exception handlers.
31 //  So we do GPU detection in a separate process (boinc --detect_gpus)
32 //  This process writes an XML file "coproc_info.xml" containing
33 //  - lists of GPU detected via CUDA and CAL
34 //  - lists of nvidia/amd/intel GPUs detected via OpenCL
35 //  - a list of other GPUs detected via OpenCL
36 //
37 //  When the process finishes, the client parses the info file.
38 //  Then for each vendor it "correlates" the GPUs, which includes:
39 //  - matching up the OpenCL and vendor-specific descriptions, if both exist
40 //  - finding the most capable GPU, and seeing which other GPUs
41 //      are similar to it in hardware and RAM.
42 //      Other GPUs are not used.
43 //  - copy these to the COPROCS structure
44 //
45 //  Also, some dual-GPU laptops (e.g., Macbook Pro) don't power
46 //  down the more powerful GPU until all applications which used
47 //  them exit. Doing GPU detection in a second, short-lived process
48 //  saves battery life on these laptops.
49 //
50 //  GPUs can also be explicitly described in cc_config.xml
51 
52 
53 #ifndef _DEBUG
54 #define USE_CHILD_PROCESS_TO_DETECT_GPUS 1
55 #endif
56 
57 #include "cpp.h"
58 
59 #ifdef _WIN32
60 #include "boinc_win.h"
61 #ifdef _MSC_VER
62 #define snprintf _snprintf
63 #define chdir _chdir
64 #endif
65 #else
66 #include "config.h"
67 #include <setjmp.h>
68 #include <signal.h>
69 #endif
70 
71 #include "coproc.h"
72 #include "gpu_detect.h"
73 #include "file_names.h"
74 #include "util.h"
75 #include "str_replace.h"
76 #include "client_msgs.h"
77 #include "client_state.h"
78 
79 using std::string;
80 using std::vector;
81 
82 #ifndef _WIN32
83 jmp_buf resume;
84 
segv_handler(int)85 void segv_handler(int) {
86     longjmp(resume, 1);
87 }
88 #endif
89 
90 vector<COPROC_ATI> ati_gpus;
91 vector<COPROC_NVIDIA> nvidia_gpus;
92 vector<COPROC_INTEL> intel_gpus;
93 vector<OPENCL_DEVICE_PROP> ati_opencls;
94 vector<OPENCL_DEVICE_PROP> nvidia_opencls;
95 vector<OPENCL_DEVICE_PROP> intel_gpu_opencls;
96 vector<OPENCL_DEVICE_PROP> other_opencls;
97 vector<OPENCL_CPU_PROP> cpu_opencls;
98 
99 static char* client_path;
100     // argv[0] from the command used to run client.
101     // May be absolute or relative.
102 static char client_dir[MAXPATHLEN];
103     // current directory at start of client
104 
get(bool use_all,vector<string> & descs,vector<string> & warnings,IGNORE_GPU_INSTANCE & ignore_gpu_instance)105 void COPROCS::get(
106     bool use_all, vector<string>&descs, vector<string>&warnings,
107     IGNORE_GPU_INSTANCE& ignore_gpu_instance
108 ) {
109 #if USE_CHILD_PROCESS_TO_DETECT_GPUS
110     int retval = 0;
111     char buf[256];
112 
113     retval = launch_child_process_to_detect_gpus();
114     if (retval) {
115         snprintf(buf, sizeof(buf),
116             "launch_child_process_to_detect_gpus() returned error %d",
117             retval
118         );
119         warnings.push_back(buf);
120     }
121     retval = read_coproc_info_file(warnings);
122     if (retval) {
123         snprintf(buf, sizeof(buf),
124             "read_coproc_info_file() returned error %d",
125             retval
126         );
127         warnings.push_back(buf);
128     }
129 #else
130     detect_gpus(warnings);
131 #endif
132     correlate_gpus(use_all, descs, ignore_gpu_instance);
133 }
134 
135 
detect_gpus(vector<string> & warnings)136 void COPROCS::detect_gpus(vector<string> &warnings) {
137 #ifdef _WIN32
138     try {
139         nvidia.get(warnings);
140     }
141     catch (...) {
142         warnings.push_back("Caught SIGSEGV in NVIDIA GPU detection");
143     }
144     try {
145         ati.get(warnings);
146     }
147     catch (...) {
148         warnings.push_back("Caught SIGSEGV in ATI GPU detection");
149     }
150     try {
151         intel_gpu.get(warnings);
152     }
153     catch (...) {
154         warnings.push_back("Caught SIGSEGV in INTEL GPU detection");
155     }
156     try {
157         // OpenCL detection must come last
158         get_opencl(warnings);
159     }
160     catch (...) {
161         warnings.push_back("Caught SIGSEGV in OpenCL detection");
162     }
163 #else
164     void (*old_sig)(int) = signal(SIGSEGV, segv_handler);
165     if (setjmp(resume)) {
166         warnings.push_back("Caught SIGSEGV in NVIDIA GPU detection");
167     } else {
168         nvidia.get(warnings);
169     }
170 
171 
172 #ifndef __APPLE__       // ATI does not yet support CAL on Macs
173     if (setjmp(resume)) {
174         warnings.push_back("Caught SIGSEGV in ATI GPU detection");
175     } else {
176         ati.get(warnings);
177     }
178 #endif
179     if (setjmp(resume)) {
180         warnings.push_back("Caught SIGSEGV in INTEL GPU detection");
181     } else {
182         intel_gpu.get(warnings);
183     }
184     if (setjmp(resume)) {
185         warnings.push_back("Caught SIGSEGV in OpenCL detection");
186     } else {
187         // OpenCL detection must come last
188         get_opencl(warnings);
189     }
190     signal(SIGSEGV, old_sig);
191 #endif
192 }
193 
194 
correlate_gpus(bool use_all,vector<string> & descs,IGNORE_GPU_INSTANCE & ignore_gpu_instance)195 void COPROCS::correlate_gpus(
196     bool use_all,
197     vector<string> &descs,
198     IGNORE_GPU_INSTANCE &ignore_gpu_instance
199 ) {
200     unsigned int i;
201     char buf[256], buf2[256];
202 
203     nvidia.correlate(use_all, ignore_gpu_instance[PROC_TYPE_NVIDIA_GPU]);
204     ati.correlate(use_all, ignore_gpu_instance[PROC_TYPE_AMD_GPU]);
205     intel_gpu.correlate(use_all, ignore_gpu_instance[PROC_TYPE_INTEL_GPU]);
206     correlate_opencl(use_all, ignore_gpu_instance);
207 
208     // NOTE: OpenCL can report a max of only 4GB.
209     //
210     for (i=0; i<cpu_opencls.size(); i++) {
211         gstate.host_info.opencl_cpu_prop[gstate.host_info.num_opencl_cpu_platforms++] = cpu_opencls[i];
212     }
213 
214     for (i=0; i<nvidia_gpus.size(); i++) {
215         // This is really CUDA description
216         nvidia_gpus[i].description(buf, sizeof(buf));
217         switch(nvidia_gpus[i].is_used) {
218         case COPROC_IGNORED:
219             snprintf(buf2, sizeof(buf2),
220                 "CUDA: NVIDIA GPU %d (ignored by config): %s",
221                 nvidia_gpus[i].device_num, buf
222             );
223             break;
224         case COPROC_USED:
225             snprintf(buf2, sizeof(buf2),
226                 "CUDA: NVIDIA GPU %d: %s",
227                 nvidia_gpus[i].device_num, buf
228             );
229             break;
230         case COPROC_UNUSED:
231         default:
232             snprintf(buf2, sizeof(buf2),
233                 "CUDA: NVIDIA GPU %d (not used): %s",
234                 nvidia_gpus[i].device_num, buf
235             );
236 
237 #ifdef __APPLE__
238             if ((nvidia_gpus[i].cuda_version >= 6050) &&
239                             nvidia_gpus[i].prop.major < 2) {
240                 // This will be called only if CUDA recognized and reported the GPU
241                 msg_printf(NULL, MSG_USER_ALERT, "NVIDIA GPU %d: %s %s",
242                     nvidia_gpus[i].device_num, nvidia_gpus[i].prop.name,
243                     _("cannot be used for CUDA or OpenCL computation with CUDA driver 6.5 or later")
244                 );
245             }
246 #endif
247             break;
248         }
249         descs.push_back(string(buf2));
250     }
251 
252     for (i=0; i<ati_gpus.size(); i++) {
253         // This is really CAL description
254         ati_gpus[i].description(buf, sizeof(buf));
255         switch(ati_gpus[i].is_used) {
256         case COPROC_IGNORED:
257             snprintf(buf2, sizeof(buf2),
258                 "CAL: ATI GPU %d (ignored by config): %s",
259                 ati_gpus[i].device_num, buf
260             );
261             break;
262         case COPROC_USED:
263             snprintf(buf2, sizeof(buf2),
264                 "CAL: ATI GPU %d: %s",
265                 ati_gpus[i].device_num, buf
266             );
267             break;
268         case COPROC_UNUSED:
269         default:
270             snprintf(buf2, sizeof(buf2),
271                 "CAL: ATI GPU %d: (not used) %s",
272                 ati_gpus[i].device_num, buf
273             );
274             break;
275         }
276         descs.push_back(string(buf2));
277     }
278 
279     // Create descriptions for OpenCL NVIDIA GPUs
280     //
281     for (i=0; i<nvidia_opencls.size(); i++) {
282         if (nvidia_opencls[i].warn_bad_cuda) {
283             // This will be called only if CUDA did _not_ recognize and report the GPU
284             msg_printf(NULL, MSG_USER_ALERT, "NVIDIA GPU %d: %s %s",
285                 nvidia_opencls[i].device_num, nvidia_opencls[i].name,
286                 _("cannot be used for CUDA or OpenCL computation with CUDA driver 6.5 or later")
287             );
288         }
289         nvidia_opencls[i].description(buf, sizeof(buf), proc_type_name(PROC_TYPE_NVIDIA_GPU));
290         descs.push_back(string(buf));
291     }
292 
293     // Create descriptions for OpenCL ATI GPUs
294     //
295     for (i=0; i<ati_opencls.size(); i++) {
296         ati_opencls[i].description(buf, sizeof(buf), proc_type_name(PROC_TYPE_AMD_GPU));
297         descs.push_back(string(buf));
298     }
299 
300     // Create descriptions for OpenCL Intel GPUs
301     //
302     for (i=0; i<intel_gpu_opencls.size(); i++) {
303         intel_gpu_opencls[i].description(buf, sizeof(buf), proc_type_name(PROC_TYPE_INTEL_GPU));
304         descs.push_back(string(buf));
305     }
306 
307     // Create descriptions for other OpenCL GPUs
308     //
309     int max_other_coprocs = MAX_RSC-1;  // coprocs[0] is reserved for CPU
310     if (have_nvidia()) max_other_coprocs--;
311     if (have_ati()) max_other_coprocs--;
312     if (have_intel_gpu()) max_other_coprocs--;
313 
314     // TODO: Should we implement cc_config ignore vectors for other (future) OpenCL coprocessors?
315 
316     for (i=0; i<other_opencls.size(); i++) {
317         if ((int)i > max_other_coprocs) {
318             other_opencls[i].is_used = COPROC_UNUSED;
319         }
320         other_opencls[i].description(buf, sizeof(buf), other_opencls[i].name);
321         descs.push_back(string(buf));
322     }
323 
324     // Create descriptions for OpenCL CPUs
325     //
326     for (i=0; i<cpu_opencls.size(); i++) {
327         cpu_opencls[i].description(buf, sizeof(buf));
328         descs.push_back(string(buf));
329     }
330 
331     ati_gpus.clear();
332     nvidia_gpus.clear();
333     intel_gpus.clear();
334     ati_opencls.clear();
335     nvidia_opencls.clear();
336     intel_gpu_opencls.clear();
337     cpu_opencls.clear();
338 }
339 
340 // This is called from CLIENT_STATE::init()
341 // after adding NVIDIA, ATI and Intel GPUs
342 // If we don't care about the order of GPUs in COPROCS::coprocs[],
343 // this code could be included at the end of COPROCS::correlate_gpus().
344 //
add_other_coproc_types()345 int COPROCS::add_other_coproc_types() {
346     int retval = 0;
347 
348     for (unsigned int i=0; i<other_opencls.size(); i++) {
349         if (other_opencls[i].is_used != COPROC_USED) continue;
350         if (n_rsc >= MAX_RSC) {
351             retval = ERR_BUFFER_OVERFLOW;
352             break;
353         }
354 
355         COPROC c;
356         // For device types other than NVIDIA, ATI or Intel GPU.
357         // we put each instance into a separate other_opencls element,
358         // so count=1.
359         //
360         c.count = 1;
361         c.opencl_device_count = 1;
362         c.opencl_prop = other_opencls[i];
363         c.available_ram = c.opencl_prop.global_mem_size;
364         c.device_num = c.opencl_prop.device_num;
365         c.peak_flops = c.opencl_prop.peak_flops;
366         c.have_opencl = true;
367         c.opencl_device_indexes[0] = c.opencl_prop.opencl_device_index;
368         c.opencl_device_ids[0] = c.opencl_prop.device_id;
369         c.instance_has_opencl[0] = true;
370         c.clear_usage();
371         safe_strcpy(c.type, other_opencls[i].name);
372 
373         // Don't call COPROCS::add() because duplicate type is legal here
374         coprocs[n_rsc++] = c;
375 
376     }
377 
378     other_opencls.clear();
379     return retval;
380 }
381 
set_path_to_client(char * path)382 void COPROCS::set_path_to_client(char *path) {
383     client_path = path;
384     // The path may be relative to the current directory
385      boinc_getcwd(client_dir);
386 }
387 
write_coproc_info_file(vector<string> & warnings)388 int COPROCS::write_coproc_info_file(vector<string> &warnings) {
389     MIOFILE mf;
390     unsigned int i, temp;
391     FILE* f;
392 
393     f = boinc_fopen(COPROC_INFO_FILENAME, "wb");
394     if (!f) return ERR_FOPEN;
395     mf.init_file(f);
396 
397     mf.printf("    <coprocs>\n");
398 
399     if (nvidia.have_cuda) {
400         mf.printf("    <have_cuda>1</have_cuda>\n");
401         mf.printf("    <cuda_version>%d</cuda_version>\n", nvidia.cuda_version);
402     }
403 
404     for (i=0; i<ati_gpus.size(); ++i) {
405        ati_gpus[i].write_xml(mf, false);
406     }
407     for (i=0; i<nvidia_gpus.size(); ++i) {
408         temp = nvidia_gpus[i].count;
409         nvidia_gpus[i].count = 1;
410         nvidia_gpus[i].pci_infos[0] = nvidia_gpus[i].pci_info;
411         nvidia_gpus[i].write_xml(mf, false);
412         nvidia_gpus[i].count = temp;
413     }
414     for (i=0; i<intel_gpus.size(); ++i) {
415         intel_gpus[i].write_xml(mf, false);
416     }
417     for (i=0; i<ati_opencls.size(); ++i) {
418         ati_opencls[i].write_xml(mf, "ati_opencl", true);
419     }
420     for (i=0; i<nvidia_opencls.size(); ++i) {
421         nvidia_opencls[i].write_xml(mf, "nvidia_opencl", true);
422     }
423     for (i=0; i<intel_gpu_opencls.size(); ++i) {
424         intel_gpu_opencls[i].write_xml(mf, "intel_gpu_opencl", true);
425     }
426     for (i=0; i<other_opencls.size(); i++) {
427         other_opencls[i].write_xml(mf, "other_opencl", true);
428     }
429     for (i=0; i<cpu_opencls.size(); i++) {
430         cpu_opencls[i].write_xml(mf);
431     }
432     for (i=0; i<warnings.size(); ++i) {
433         mf.printf("<warning>%s</warning>\n", warnings[i].c_str());
434     }
435 
436     mf.printf("    </coprocs>\n");
437     fclose(f);
438     return 0;
439 }
440 
read_coproc_info_file(vector<string> & warnings)441 int COPROCS::read_coproc_info_file(vector<string> &warnings) {
442     MIOFILE mf;
443     int retval;
444     FILE* f;
445     string s;
446 
447     COPROC_ATI my_ati_gpu;
448     COPROC_NVIDIA my_nvidia_gpu;
449     COPROC_INTEL my_intel_gpu;
450     OPENCL_DEVICE_PROP ati_opencl;
451     OPENCL_DEVICE_PROP nvidia_opencl;
452     OPENCL_DEVICE_PROP intel_gpu_opencl;
453     OPENCL_DEVICE_PROP other_opencl;
454     OPENCL_CPU_PROP cpu_opencl;
455 
456     ati_gpus.clear();
457     nvidia_gpus.clear();
458     intel_gpus.clear();
459     ati_opencls.clear();
460     nvidia_opencls.clear();
461     intel_gpu_opencls.clear();
462     other_opencls.clear();
463     cpu_opencls.clear();
464 
465     f = boinc_fopen(COPROC_INFO_FILENAME, "r");
466     if (!f) return ERR_FOPEN;
467     XML_PARSER xp(&mf);
468     mf.init_file(f);
469     if (!xp.parse_start("coprocs")) {
470         fclose(f);
471         return ERR_XML_PARSE;
472     }
473 
474     while (!xp.get_tag()) {
475         if (xp.match_tag("/coprocs")) {
476             fclose(f);
477             return 0;
478         }
479 
480         if (xp.parse_bool("have_cuda", nvidia.have_cuda)) continue;
481         if (xp.parse_int("cuda_version", nvidia.cuda_version)) {
482              continue;
483         }
484 
485         if (xp.match_tag("coproc_ati")) {
486             retval = my_ati_gpu.parse(xp);
487             if (retval) {
488                 my_ati_gpu.clear();
489             } else {
490                 my_ati_gpu.device_num = (int)ati_gpus.size();
491                 ati_gpus.push_back(my_ati_gpu);
492             }
493             continue;
494         }
495         if (xp.match_tag("coproc_cuda")) {
496             retval = my_nvidia_gpu.parse(xp);
497             if (retval) {
498                 my_nvidia_gpu.clear();
499             } else {
500                 my_nvidia_gpu.device_num = (int)nvidia_gpus.size();
501                 my_nvidia_gpu.pci_info = my_nvidia_gpu.pci_infos[0];
502                 memset(&my_nvidia_gpu.pci_infos[0], 0, sizeof(struct PCI_INFO));
503                 nvidia_gpus.push_back(my_nvidia_gpu);
504             }
505             continue;
506         }
507         if (xp.match_tag("coproc_intel_gpu")) {
508             retval = my_intel_gpu.parse(xp);
509             if (retval) {
510                 my_intel_gpu.clear();
511             } else {
512                 my_intel_gpu.device_num = (int)intel_gpus.size();
513                 intel_gpus.push_back(my_intel_gpu);
514             }
515             continue;
516         }
517 
518         if (xp.match_tag("ati_opencl")) {
519             memset(&ati_opencl, 0, sizeof(ati_opencl));
520             retval = ati_opencl.parse(xp, "/ati_opencl");
521             if (retval) {
522                 memset(&ati_opencl, 0, sizeof(ati_opencl));
523             } else {
524                 ati_opencl.is_used = COPROC_IGNORED;
525                 ati_opencls.push_back(ati_opencl);
526             }
527             continue;
528         }
529 
530         if (xp.match_tag("nvidia_opencl")) {
531             memset(&nvidia_opencl, 0, sizeof(nvidia_opencl));
532             retval = nvidia_opencl.parse(xp, "/nvidia_opencl");
533             if (retval) {
534                 memset(&nvidia_opencl, 0, sizeof(nvidia_opencl));
535             } else {
536                 nvidia_opencl.is_used = COPROC_IGNORED;
537                 nvidia_opencls.push_back(nvidia_opencl);
538             }
539             continue;
540         }
541 
542         if (xp.match_tag("intel_gpu_opencl")) {
543             memset(&intel_gpu_opencl, 0, sizeof(intel_gpu_opencl));
544             retval = intel_gpu_opencl.parse(xp, "/intel_gpu_opencl");
545             if (retval) {
546                 memset(&intel_gpu_opencl, 0, sizeof(intel_gpu_opencl));
547             } else {
548                 intel_gpu_opencl.is_used = COPROC_IGNORED;
549                 intel_gpu_opencls.push_back(intel_gpu_opencl);
550             }
551             continue;
552         }
553 
554         if (xp.match_tag("other_opencl")) {
555             memset(&other_opencl, 0, sizeof(other_opencl));
556             retval = other_opencl.parse(xp, "/other_opencl");
557             if (retval) {
558                 memset(&other_opencl, 0, sizeof(other_opencl));
559             } else {
560                 other_opencl.is_used = COPROC_USED;
561                 other_opencls.push_back(other_opencl);
562             }
563             continue;
564         }
565 
566         if (xp.match_tag("opencl_cpu_prop")) {
567             memset(&cpu_opencl, 0, sizeof(cpu_opencl));
568             retval = cpu_opencl.parse(xp);
569             if (retval) {
570                 memset(&cpu_opencl, 0, sizeof(cpu_opencl));
571             } else {
572                 cpu_opencl.opencl_prop.is_used = COPROC_IGNORED;
573                 cpu_opencls.push_back(cpu_opencl);
574             }
575             continue;
576         }
577 
578         if (xp.parse_string("warning", s)) {
579             warnings.push_back(s);
580             continue;
581         }
582 
583         // TODO: parse OpenCL info for CPU when implemented:
584         //  gstate.host_info.have_cpu_opencl
585         //  gstate.host_info.cpu_opencl_prop
586     }
587 
588     fclose(f);
589     return ERR_XML_PARSE;
590 }
591 
launch_child_process_to_detect_gpus()592 int COPROCS::launch_child_process_to_detect_gpus() {
593 #ifdef _WIN32
594     HANDLE prog;
595 #else
596     int prog;
597 #endif
598     char quoted_data_dir[MAXPATHLEN+2];
599     char data_dir[MAXPATHLEN];
600     int retval = 0;
601 
602     retval = boinc_delete_file(COPROC_INFO_FILENAME);
603     if (retval) {
604         msg_printf(0, MSG_INFO,
605             "Failed to delete old %s. error code %d",
606             COPROC_INFO_FILENAME, retval
607         );
608     } else {
609         for (;;) {
610             if (!boinc_file_exists(COPROC_INFO_FILENAME)) break;
611             boinc_sleep(0.01);
612         }
613     }
614 
615     boinc_getcwd(data_dir);
616 
617 #ifdef _WIN32
618     strlcpy(quoted_data_dir, "\"", sizeof(quoted_data_dir));
619     strlcat(quoted_data_dir, data_dir, sizeof(quoted_data_dir));
620     strlcat(quoted_data_dir, "\"", sizeof(quoted_data_dir));
621 #else
622     strlcpy(quoted_data_dir, data_dir, sizeof(quoted_data_dir));
623 #endif
624 
625     if (log_flags.coproc_debug) {
626         msg_printf(0, MSG_INFO,
627             "[coproc] launching child process at %s",
628             client_path
629         );
630         msg_printf(0, MSG_INFO,
631             "[coproc] relative to directory %s",
632             client_dir
633         );
634         msg_printf(0, MSG_INFO,
635             "[coproc] with data directory %s",
636             quoted_data_dir
637         );
638     }
639 
640     int argc = 4;
641     char* const argv[5] = {
642 #ifdef _WIN32
643          const_cast<char *>("boinc.exe"),
644 #else
645          const_cast<char *>("boinc"),
646 #endif
647          const_cast<char *>("--detect_gpus"),
648          const_cast<char *>("--dir"),
649          const_cast<char *>(quoted_data_dir),
650          NULL
651     };
652 
653     chdir(client_dir);
654 
655     retval = run_program(
656         client_dir,
657         client_path,
658         argc,
659         argv,
660 #ifdef _DEBUG
661         1,
662 #else
663         0,
664 #endif
665         prog
666     );
667 
668     chdir(data_dir);
669 
670     if (retval) {
671         if (log_flags.coproc_debug) {
672             msg_printf(0, MSG_INFO,
673                 "[coproc] run_program of child process returned error %d",
674                 retval
675             );
676         }
677         return retval;
678     }
679 
680     retval = get_exit_status(prog);
681     if (retval) {
682         msg_printf(0, MSG_INFO,
683             "GPU detection failed. error code %d",
684             retval
685         );
686     }
687 
688     return 0;
689 }
690 
691 // print descriptions of coprocs specified in cc_config.xml,
692 // and make sure counts are <= 64
693 //
bound_counts()694 void COPROCS::bound_counts() {
695     for (int j=1; j<n_rsc; j++) {
696         msg_printf(NULL, MSG_INFO, "Coprocessor specified in cc_config.xml. Type %s (%s); count %d",
697             coprocs[j].type,
698             coprocs[j].non_gpu?"non-GPU":"GPU",
699             coprocs[j].count
700         );
701         if (coprocs[j].count > MAX_COPROC_INSTANCES) {
702             msg_printf(NULL, MSG_USER_ALERT,
703                 "%d instances of %s specified in cc_config.xml; max is %d",
704                 coprocs[j].count,
705                 coprocs[j].type,
706                 MAX_COPROC_INSTANCES
707             );
708             coprocs[j].count = MAX_COPROC_INSTANCES;
709         }
710     }
711 }
712