Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line | 
|---|---|---|---|
| 360 | f9daq | 1 | #include <stdlib.h>  | 
        
| 2 | #include <stdio.h>  | 
        ||
| 3 | #include <signal.h>  | 
        ||
| 4 | #include <time.h>  | 
        ||
| 5 | #include <string.h>  | 
        ||
| 6 | #include <sys/types.h>  | 
        ||
| 7 | #include <sys/time.h>  | 
        ||
| 8 | #include <ctype.h>  | 
        ||
| 9 | #include <unistd.h> | 
        ||
| 10 | |||
| 11 | #ifdef MRP | 
        ||
| 12 | #include <rp.h> | 
        ||
| 13 | #else | 
        ||
| 14 | #include "calib.h" | 
        ||
| 15 | #endif | 
        ||
| 16 | |||
| 17 | #include "fpga_osc.h"  | 
        ||
| 18 | |||
| 19 | #define TRUE -1  | 
        ||
| 20 | #define FALSE 0 | 
        ||
| 21 | |||
| 22 | int timer_out; struct sigaction oact;  | 
        ||
| 23 | |||
| 24 | int ctrl_c=0;  | 
        ||
| 25 | |||
| 26 | void SigInt (int sig) {  | 
        ||
| 27 | ctrl_c = 1;  | 
        ||
| 28 | } | 
        ||
| 29 | |||
| 30 | |||
| 31 | void timerast (signumber, code, context) int signumber, code; struct sigcontext context; {  | 
        ||
| 32 | fprintf(stderr, "Timeout\n");  | 
        ||
| 33 | timer_out = TRUE;  | 
        ||
| 34 | } | 
        ||
| 35 | |||
| 36 | void tmlnk (tout) int tout; {  | 
        ||
| 37 | struct sigaction act;  | 
        ||
| 38 | struct itimerval tdelay;  | 
        ||
| 39 | |||
| 40 | act.sa_handler = timerast;  | 
        ||
| 41 | sigemptyset (&act.sa_mask);  | 
        ||
| 42 | act.sa_flags = 0;  | 
        ||
| 43 | |||
| 44 | tdelay.it_value.tv_sec = tout / 100;  | 
        ||
| 45 | tdelay.it_value.tv_usec = 10000 * (tout % 100);  | 
        ||
| 46 | tdelay.it_interval.tv_sec = 0;  | 
        ||
| 47 | tdelay.it_interval.tv_usec = 0;  | 
        ||
| 48 | |||
| 49 | if (sigaction (SIGALRM, &act, &oact) < 0)  | 
        ||
| 50 |   { | 
        ||
| 51 | perror ("sigaction(tmlnk)");  | 
        ||
| 52 | exit (EXIT_FAILURE);  | 
        ||
| 53 |   } | 
        ||
| 54 | if (setitimer (ITIMER_REAL, &tdelay, NULL) < 0)  | 
        ||
| 55 |   { | 
        ||
| 56 | perror ("setitimer(tmlnk)");  | 
        ||
| 57 | exit (EXIT_FAILURE);  | 
        ||
| 58 |   } | 
        ||
| 59 | } | 
        ||
| 60 | |||
| 61 | void tmulk () {  | 
        ||
| 62 | struct itimerval tdelay;  | 
        ||
| 63 | |||
| 64 | tdelay.it_value.tv_sec = 0;  | 
        ||
| 65 | tdelay.it_value.tv_usec = 0;  | 
        ||
| 66 | tdelay.it_interval.tv_sec = 0;  | 
        ||
| 67 | tdelay.it_interval.tv_usec = 0;  | 
        ||
| 68 | |||
| 69 | if (setitimer (ITIMER_REAL, &tdelay, NULL) < 0)  | 
        ||
| 70 |   { | 
        ||
| 71 | perror ("setitimer(tmulk)");  | 
        ||
| 72 | exit (EXIT_FAILURE);  | 
        ||
| 73 |   } | 
        ||
| 74 | if (sigaction (SIGALRM, &oact, NULL) < 0)  | 
        ||
| 75 |   { | 
        ||
| 76 | perror ("sigaction(tmulk)");  | 
        ||
| 77 | exit (EXIT_FAILURE);  | 
        ||
| 78 |   } | 
        ||
| 79 | } | 
        ||
| 80 | |||
| 81 | #ifndef MRP | 
        ||
| 82 | rp_calib_params_t rp_calib_params; /** Pointer to externally defined calibration parameters. */ rp_calib_params_t *gen_calib_params = NULL;  | 
        ||
| 83 | #endif | 
        ||
| 84 | int daq_init (char * buff) {  | 
        ||
| 85 | #ifdef DEBUG | 
        ||
| 86 | fprintf (stderr, "Server: init\n");  | 
        ||
| 87 | #endif | 
        ||
| 88 | int * hdr = (int *) buff;  | 
        ||
| 89 | int delay = hdr[0];  | 
        ||
| 90 | int decimation = hdr[1];  | 
        ||
| 91 | float threshold_voltage = hdr[2]/1000.;  | 
        ||
| 92 | fprintf(stderr, "delay = %d\tdecimation = %d\tthreshold = %f\n", delay, decimation, threshold_voltage);  | 
        ||
| 93 | |||
| 94 | /* | 
        ||
| 95 | |||
| 96 |    rp_default_calib_params(&rp_calib_params); | 
        ||
| 97 |    gen_calib_params = &rp_calib_params; | 
        ||
| 98 |    if(rp_read_calib_params(gen_calib_params) < 0) { | 
        ||
| 99 |        fprintf(stderr, "rp_read_calib_params() failed, using default" | 
        ||
| 100 |            " parameters\n"); | 
        ||
| 101 |    } | 
        ||
| 102 | |||
| 103 | */ | 
        ||
| 104 | |||
| 105 |   // use this to acquire calibrated offset: int offset = gen_calib_params->fe_ch1_dc_offs; | 
        ||
| 106 | #ifdef MRP | 
        ||
| 107 | if(rp_Init() != RP_OK){  | 
        ||
| 108 | fprintf(stderr, "Rp api init failed!\n");  | 
        ||
| 109 |         } | 
        ||
| 110 | rp_AcqReset();  | 
        ||
| 111 | const int rpdecimation[6]={RP_DEC_1 ,RP_DEC_8 ,RP_DEC_64,  | 
        ||
| 112 | RP_DEC_1024 ,RP_DEC_8192 ,RP_DEC_65536 };  | 
        ||
| 113 | rp_AcqSetDecimation(rpdecimation[decimation%6]);  | 
        ||
| 114 | const int c[2] = {RP_CH_1, RP_CH_2};  | 
        ||
| 115 | rp_AcqSetTriggerLevel(c[0],threshold_voltage); //Trig level is set in Volts while in SCPI  | 
        ||
| 116 | rp_AcqSetTriggerLevel(c[1],threshold_voltage);  | 
        ||
| 117 | rp_AcqSetTriggerDelay(delay);  | 
        ||
| 118 | return 0;  | 
        ||
| 119 | #else | 
        ||
| 120 |   // initialization | 
        ||
| 121 | int start = osc_fpga_init();  | 
        ||
| 122 | if(start)  | 
        ||
| 123 |   { | 
        ||
| 124 | printf("osc_fpga_init didn't work, retval = %d",start);  | 
        ||
| 125 | return -1;  | 
        ||
| 126 |   } | 
        ||
| 127 | |||
| 128 |   // set acquisition parameters | 
        ||
| 129 | osc_fpga_set_trigger_delay(delay);  | 
        ||
| 130 | g_osc_fpga_reg_mem->data_dec = decimation;  | 
        ||
| 131 | osc_fpga_reset();  | 
        ||
| 132 | g_osc_fpga_reg_mem->chb_thr = osc_fpga_cnv_v_to_cnt(threshold_voltage); //sets trigger voltage  | 
        ||
| 133 | #endif | 
        ||
| 134 | fprintf(stderr, "%s : %d\n", __FILE__, __LINE__);  | 
        ||
| 135 | return 0;  | 
        ||
| 136 | } | 
        ||
| 137 | |||
| 138 | int daq_end () {  | 
        ||
| 139 | #ifdef DEBUG | 
        ||
| 140 | fprintf (stderr, "Server: end\n");  | 
        ||
| 141 | #endif | 
        ||
| 142 | #ifdef MRP | 
        ||
| 143 | rp_Release();  | 
        ||
| 144 | #else | 
        ||
| 145 | osc_fpga_exit();  | 
        ||
| 146 | #endif | 
        ||
| 147 | return 0;  | 
        ||
| 148 | } | 
        ||
| 149 | |||
| 150 | int daq_clear () {  | 
        ||
| 151 | #ifdef DEBUG | 
        ||
| 152 | fprintf (stderr, "Server: clear\n");  | 
        ||
| 153 | #endif | 
        ||
| 154 | return 0;  | 
        ||
| 155 | } | 
        ||
| 156 | |||
| 157 | |||
| 158 | int16_t chdata[16*1024];  | 
        ||
| 159 | float * chfdata = (float *) chdata;  | 
        ||
| 160 | int daq_run (const char *par, char ** data, int *maxlen)  | 
        ||
| 161 | |||
| 162 | { | 
        ||
| 163 | |||
| 164 | int *data_buf;  | 
        ||
| 165 | int neve;  | 
        ||
| 166 | |||
| 167 | unsigned short *sbuff = (unsigned short *) (par);  | 
        ||
| 168 | unsigned short maxeve = sbuff[0];  | 
        ||
| 169 | unsigned short nsamples = sbuff[1];  | 
        ||
| 170 | unsigned short tout = sbuff[2];  | 
        ||
| 171 | unsigned char trigger = par[6];  | 
        ||
| 172 | unsigned char chmask = par[7];  | 
        ||
| 173 | clock_t t,t0;  | 
        ||
| 174 | neve = 0;  | 
        ||
| 175 | |||
| 176 | t0=clock();  | 
        ||
| 177 | int eventsize = 0;  | 
        ||
| 178 | if (chmask & 0x1) eventsize += (nsamples+2);  | 
        ||
| 179 | if (chmask & 0x2) eventsize += (nsamples+2);  | 
        ||
| 180 | eventsize+=4;  | 
        ||
| 181 | int required_size = eventsize * maxeve+3;  | 
        ||
| 182 | |||
| 183 | #ifdef DEBUG | 
        ||
| 184 |         time_t mtime; | 
        ||
| 185 | time(&mtime);  | 
        ||
| 186 | fprintf (stderr, "daq_run:\tmaxeve %d\tnsamples=%d\ttimeout=%d\ttrigger=%d\tchmask=%d\t%s", maxeve ,nsamples,tout,trigger,chmask, ctime(&mtime)); //  | 
        ||
| 187 | #endif | 
        ||
| 188 | |||
| 189 | |||
| 190 | if (required_size > *maxlen ) {  | 
        ||
| 191 | free (*data);  | 
        ||
| 192 | *data = (char *) malloc(required_size *sizeof(int));  | 
        ||
| 193 | fprintf(stderr, "New Buffer with size %d allocated. Old size %d\n", required_size, *maxlen);  | 
        ||
| 194 | *maxlen = required_size;  | 
        ||
| 195 |    } | 
        ||
| 196 | |||
| 197 | int *ibuf = (int *) (*data);  | 
        ||
| 198 | |||
| 199 | |||
| 200 | data_buf = ibuf + 3;  | 
        ||
| 201 | const int sleeptime = 135*nsamples/16386; //135 us for 16386 samples and to fill the 16k ADC buffer/  | 
        ||
| 202 | |||
| 203 | for (int ieve=0; ieve < maxeve; ieve++)  | 
        ||
| 204 |     { | 
        ||
| 205 | |||
| 206 | #ifdef MRP | 
        ||
| 207 | |||
| 208 | rp_AcqStart();  | 
        ||
| 209 | usleep(sleeptime);  | 
        ||
| 210 | rp_AcqSetTriggerSrc(trigger);  | 
        ||
| 211 | timer_out = FALSE;  | 
        ||
| 212 | tmlnk (tout);  | 
        ||
| 213 | rp_acq_trig_src_t source; | 
        ||
| 214 | do {  | 
        ||
| 215 | rp_AcqGetTriggerSrc(&source);  | 
        ||
| 216 |   //printf("TRG %d src %d\n", trigger, source);   | 
        ||
| 217 | if (timer_out || ctrl_c) break;  | 
        ||
| 218 | } while (source == trigger);  | 
        ||
| 219 | |||
| 220 | tmulk ();  | 
        ||
| 221 | usleep(sleeptime);  | 
        ||
| 222 | |||
| 223 | |||
| 224 | #else | 
        ||
| 225 | |||
| 226 | osc_fpga_arm_trigger();  | 
        ||
| 227 | usleep(sleeptime);  | 
        ||
| 228 | osc_fpga_set_trigger(trigger);  | 
        ||
| 229 | |||
| 230 | while (g_osc_fpga_reg_mem->trig_source != 0){  | 
        ||
| 231 | if (timer_out || ctrl_c) break;  | 
        ||
| 232 |        } | 
        ||
| 233 |        // with this loop the program waits until the acquistion is completed before continue. | 
        ||
| 234 | int trig_ptr = g_osc_fpga_reg_mem->wr_ptr_trigger; // get pointer to mem. adress where trigger was met  | 
        ||
| 235 | int * ch_signal[2];  | 
        ||
| 236 | osc_fpga_get_sig_ptr(&ch_signal[0], &ch_signal[1]);  | 
        ||
| 237 | #endif | 
        ||
| 238 | *(data_buf++) = 0x2;  | 
        ||
| 239 | *(data_buf++) = chmask;  | 
        ||
| 240 | |||
| 241 | for (int id = 0;id<2;id++){  | 
        ||
| 242 | if ( !(chmask & (1 << id)) ) continue;  | 
        ||
| 243 | |||
| 244 | *(data_buf++) = id;  | 
        ||
| 245 | *(data_buf++) = nsamples;  | 
        ||
| 246 | #ifdef MRP | 
        ||
| 247 | const int c[2] = {RP_CH_1, RP_CH_2};  | 
        ||
| 248 | unsigned int isamples = nsamples;  | 
        ||
| 249 | rp_AcqGetLatestDataV(c[id], &isamples, (float *) data_buf );  | 
        ||
| 250 | data_buf+=nsamples;  | 
        ||
| 251 | #else | 
        ||
| 252 | |||
| 253 | const int BUF = 16*1024;  | 
        ||
| 254 | const int offset = 0;  | 
        ||
| 255 | if (trig_ptr > (BUF-nsamples)) // Enter logic to transition from end to beginning of cha_signal buffer.  | 
        ||
| 256 |          { | 
        ||
| 257 | for (int i=trig_ptr;i<BUF;i++) *(data_buf++) = ch_signal[id][i]-offset;  | 
        ||
| 258 | for (int i=0;i<nsamples-(BUF-trig_ptr);i++) *(data_buf++) = ch_signal[id][i]-offset;  | 
        ||
| 259 |          } | 
        ||
| 260 | else // Enter simple logic to send sampleSize from trigger point  | 
        ||
| 261 |          { | 
        ||
| 262 | for (int i=0;i<nsamples;i++) *(data_buf++) = ch_signal[id][trig_ptr + i]-offset;  | 
        ||
| 263 |          } | 
        ||
| 264 | #endif | 
        ||
| 265 |        } | 
        ||
| 266 | *(data_buf++) = 0x3;  | 
        ||
| 267 | *(data_buf++) = neve;  | 
        ||
| 268 |        neve++; | 
        ||
| 269 | if (ieve+1 % 500 == 0) fprintf(stderr, "Event %d\n", ieve);  | 
        ||
| 270 | if (timer_out) break;  | 
        ||
| 271 | |||
| 272 |     } | 
        ||
| 273 | |||
| 274 | |||
| 275 | |||
| 276 | int *len = ibuf;  | 
        ||
| 277 | int *nev = ibuf + 1;  | 
        ||
| 278 | float *dt = (float *)(ibuf + 2);  | 
        ||
| 279 | |||
| 280 | *len = (data_buf-len)*sizeof(int);  | 
        ||
| 281 | *nev = neve;  | 
        ||
| 282 | t = clock();  | 
        ||
| 283 | *dt = t-t0;  | 
        ||
| 284 | *dt /= CLOCKS_PER_SEC;  | 
        ||
| 285 | |||
| 286 | return *len;  | 
        ||
| 287 | } | 
        ||
| 288 | |||
| 289 | #define MAXLEN 0XFFFF | 
        ||
| 290 | struct RUNHDR {  | 
        ||
| 291 | unsigned short neve;  | 
        ||
| 292 | unsigned short nsamples;  | 
        ||
| 293 | unsigned short tout;  | 
        ||
| 294 | unsigned char trigger;  | 
        ||
| 295 | unsigned char mask;  | 
        ||
| 296 | int nloops ;  | 
        ||
| 297 | int data [MAXLEN];  | 
        ||
| 298 | } ;  | 
        ||
| 299 | |||
| 300 | |||
| 301 | struct INIHDR {  | 
        ||
| 302 | int delay ;  | 
        ||
| 303 | int decimation;  | 
        ||
| 304 | int threshold;  | 
        ||
| 305 | };  | 
        ||
| 306 | |||
| 307 | |||
| 308 | int daq_help(char *fname, struct INIHDR * i, struct RUNHDR *r, int verbosity){  | 
        ||
| 309 | |||
| 310 | printf("-o filename ... output filename %s\n", fname);  | 
        ||
| 311 | printf("-v verbose ... verbosity %d\n", verbosity);  | 
        ||
| 312 | printf("-b decimation ... decimation %d\n", i->decimation);  | 
        ||
| 313 | printf("-i timeout ... interrupt timeout %d\n", r->tout);  | 
        ||
| 314 | printf("-s nsamples ... number of samples %d\n", r->nsamples);  | 
        ||
| 315 | printf("-d delay ... delay %d\n", i->delay);  | 
        ||
| 316 | printf("-t trigger ... trigger type %d\n", r->trigger);  | 
        ||
| 317 | printf("-l level ... trigger level %f\n", i->threshold*0.001);  | 
        ||
| 318 | printf("-m mask ... channel mask %d\n", r->mask );  | 
        ||
| 319 | printf("-n neve ... number of events per call %d\n", r->neve);  | 
        ||
| 320 | printf("-r nloops ... number of calls %d\n", r->nloops);  | 
        ||
| 321 | return 0;  | 
        ||
| 322 | } | 
        ||
| 323 | |||
| 324 | |||
| 325 | |||
| 326 | int daq_main( int argc , char ** argv) {  | 
        ||
| 327 |   // intercept routine | 
        ||
| 328 | if (signal (SIGINT, SigInt) == SIG_ERR) {  | 
        ||
| 329 | perror ("sigignore");  | 
        ||
| 330 |   } | 
        ||
| 331 | |||
| 332 | |||
| 333 | char filename[0xFF]="";  | 
        ||
| 334 | |||
| 335 | |||
| 336 | struct INIHDR inihdr = {  | 
        ||
| 337 | .decimation = 1,  | 
        ||
| 338 | .threshold = 100,  | 
        ||
| 339 | .delay = 1024  | 
        ||
| 340 | };  | 
        ||
| 341 | |||
| 342 | |||
| 343 | |||
| 344 | struct RUNHDR runhdr;  | 
        ||
| 345 | |||
| 346 | |||
| 347 | runhdr.neve =10000;  | 
        ||
| 348 | runhdr.nsamples = 1024;  | 
        ||
| 349 | runhdr.tout = 1000;  | 
        ||
| 350 | runhdr.trigger = 1;  | 
        ||
| 351 | runhdr.mask = 0x1;  | 
        ||
| 352 | runhdr.nloops = 1;  | 
        ||
| 353 | |||
| 354 | int verbose = 0;  | 
        ||
| 355 | int argdata = 0;  | 
        ||
| 356 | |||
| 357 | if (argc <2) {  | 
        ||
| 358 | daq_help(filename, &inihdr, &runhdr, verbose);  | 
        ||
| 359 | exit(-1);  | 
        ||
| 360 |   } | 
        ||
| 361 | |||
| 362 | opterr = 0;  | 
        ||
| 363 | int c;  | 
        ||
| 364 | while ((c = getopt (argc, argv, "o:v:b:i:s:d:t:l:m:n:r:h")) != -1)  | 
        ||
| 365 | switch (c) {  | 
        ||
| 366 | case 'o':  | 
        ||
| 367 | sprintf(filename,"%s", optarg );  | 
        ||
| 368 | break; // output  | 
        ||
| 369 | case 'v':  | 
        ||
| 370 | verbose = strtoul (optarg,NULL,0);  | 
        ||
| 371 | break; // verbosity  | 
        ||
| 372 | case 'b':  | 
        ||
| 373 | inihdr.decimation = strtoul (optarg,NULL,0);  | 
        ||
| 374 | break;  | 
        ||
| 375 | case 'i':  | 
        ||
| 376 | runhdr.tout = strtoul (optarg,NULL,0);  | 
        ||
| 377 | break;  | 
        ||
| 378 | case 's':  | 
        ||
| 379 | runhdr.nsamples = strtoul (optarg,NULL,0);  | 
        ||
| 380 | break;  | 
        ||
| 381 | case 'd':  | 
        ||
| 382 | inihdr.delay = strtoul (optarg,NULL,0);  | 
        ||
| 383 | break;  | 
        ||
| 384 | case 't':  | 
        ||
| 385 | runhdr.trigger = strtoul (optarg,NULL,0);  | 
        ||
| 386 | break;  | 
        ||
| 387 | case 'm':  | 
        ||
| 388 | runhdr.mask = strtoul (optarg,NULL,0);  | 
        ||
| 389 | break;  | 
        ||
| 390 | case 'n':  | 
        ||
| 391 | runhdr.neve = atoi (optarg);  | 
        ||
| 392 | break;  | 
        ||
| 393 | case 'r':  | 
        ||
| 394 | runhdr.nloops = atoi (optarg);  | 
        ||
| 395 | break;  | 
        ||
| 396 | case 'l':  | 
        ||
| 397 | inihdr.threshold = (int) (1000*atof (optarg));  | 
        ||
| 398 | break;  | 
        ||
| 399 | case 'h':  | 
        ||
| 400 | daq_help(filename, &inihdr, &runhdr, verbose);  | 
        ||
| 401 | break;  | 
        ||
| 402 | case '?':  | 
        ||
| 403 | if (optopt == 'c')  | 
        ||
| 404 | fprintf (stderr, "Option -%c requires an argument.\n", optopt);  | 
        ||
| 405 | else if (isprint (optopt))  | 
        ||
| 406 | fprintf (stderr, "Unknown option `-%c'.\n", optopt);  | 
        ||
| 407 |         else | 
        ||
| 408 | fprintf (stderr,  | 
        ||
| 409 | "Unknown option character `\\x%x'.\n",  | 
        ||
| 410 | optopt);  | 
        ||
| 411 | return 1;  | 
        ||
| 412 | default:  | 
        ||
| 413 | abort ();  | 
        ||
| 414 |     } | 
        ||
| 415 | for (int i=optind; i<argc; i++) argdata = strtoul (argv[i],NULL,0);  | 
        ||
| 416 | if (verbose) daq_help(filename, &inihdr, &runhdr, verbose);  | 
        ||
| 417 | |||
| 418 | printf("argdata %d nloops %d\n",argdata,runhdr.nloops);  | 
        ||
| 419 | |||
| 420 | daq_init((char *)&inihdr);  | 
        ||
| 421 | FILE *fp=NULL;  | 
        ||
| 422 | if (strlen(filename)>0) fp = fopen(filename, "wb");  | 
        ||
| 423 | |||
| 424 | |||
| 425 | int maxlen = MAXLEN;  | 
        ||
| 426 | char *data = (char *) malloc(maxlen * sizeof(int));  | 
        ||
| 427 | time_t t,tstart;  | 
        ||
| 428 | time(&tstart);  | 
        ||
| 429 | |||
| 430 | for (int i=0;i< runhdr.nloops;i++){  | 
        ||
| 431 | int nb =daq_run((const char*) &runhdr, &data, &maxlen);  | 
        ||
| 432 | if (ctrl_c) break;  | 
        ||
| 433 | time(&t);  | 
        ||
| 434 | fprintf(stderr, "Loop %d dt=%d s\n", i, (int)(t-tstart));  | 
        ||
| 435 | |||
| 436 | if (fp) {  | 
        ||
| 437 | fprintf(stderr, "Writing %d to %s", nb, filename);  | 
        ||
| 438 | fwrite(data, 1, nb, fp);  | 
        ||
| 439 |     } | 
        ||
| 440 | |||
| 441 |   } | 
        ||
| 442 | time(&t);  | 
        ||
| 443 | fprintf(stderr, "Total events %d in %d s\n", runhdr.nloops*runhdr.neve, (int)(t-tstart));  | 
        ||
| 444 | if (fp) fclose(fp);  | 
        ||
| 445 | if (data!=NULL) free(data);  | 
        ||
| 446 | return 0;  | 
        ||
| 447 | } |