BOROSOFTWAREAll work →
scantool/diag_l2.c 636 lines
1 finding in this fileB22L140
1/*
2 * freediag - Vehicle Diagnostic Utility
3 *
4 *
5 * Copyright (C) 2001 Richard Almeida & Ibex Ltd ([email protected])
6 * 2009-2016 fenugrec
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 *************************************************************************
23 *
24 * L2 diagnostic interface, generic routines
25 *
26 * This sits "under" the L2 per-protocol (such as ISO 14230, SAE J1979)
27 * - understands the protocol format,
28 * - pads messages as needed,
29 * - and sends "tester present" messages at the correct intervals to keep
30 * the link to an ECU alive
31 *
32 */
33
34#include <stdbool.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <assert.h>
38
39#include "diag.h"
40#include "diag_l0.h"
41#include "diag_l1.h"
42#include "diag_os.h"
43#include "diag_err.h"
44
45#include "diag_l2.h"
46#include "utlist.h"
47
48
49int diag_l2_debug;
50
51
52/* struct to manage L2 stuff, used in here only */
53static struct {
54 diag_mtx connlist_mtx; // mutex for accessing dl2conn_list
55 struct diag_l2_conn *dl2conn_list; // linked-list of current diag_l2_conn-s
56 struct diag_l2_link *dl2l_list; // linked-list of current L2-L0 links
57 bool init_done;
58} l2internal = {
59 .dl2conn_list = NULL,
60 .dl2l_list = NULL, // linked-list of current L2-L0 links
61 .init_done = false
62};
63
64
65/** Find an existing L2 link using the specified L0 device.
66 * @return NULL if not found
67 */
68static struct diag_l2_link *diag_l2_findlink(const struct diag_l0_device *dl0d) {
69 struct diag_l2_link *dl2l=NULL;
70
71 LL_FOREACH(l2internal.dl2l_list, dl2l) {
72 if (dl2l->l2_dl0d == dl0d) {
73 break;
74 }
75 }
76
77 return dl2l;
78}
79
80/*
81 *
82 * remove a L2 connection from our list
83 * - up to the caller to have shut it down properly first
84 * Always returns 0
85 */
86
87static int diag_l2_rmconn(struct diag_l2_conn *dl2c) {
88 assert(dl2c !=NULL);
89
90 diag_os_lock(&l2internal.connlist_mtx);
91 LL_DELETE(l2internal.dl2conn_list, dl2c);
92 diag_os_unlock(&l2internal.connlist_mtx);
93
94 return 0;
95}
96
97/*
98 * Called regularly to check timeouts etc (call at least once per
99 * second)
100 * Note: This is called from a signal handler.
101 *
102 * XXX Uses functions not async-signal-safe.
103 * This parses through the l2internal.dl2conn_list linked-list and
104 * calls the ->diag_l2_proto_timeout function for every dl2conn
105 * that has expired.
106 *
107 * In order for this to work well, all L2
108 */
109void diag_l2_timer(void) {
110 struct diag_l2_conn *d_l2_conn;
111
112 unsigned long now;
113
114 now = diag_os_getms(); /* XXX probably Not async safe */
115
116 if (periodic_done() || !diag_os_trylock(&l2internal.connlist_mtx)) {
117 return;
118 }
119
120 LL_FOREACH(l2internal.dl2conn_list, d_l2_conn) {
121 bool expired = 0;
122
123 /*
124 * If in monitor mode, or the connection isn't open,
125 * or L1 does the keepalive, do nothing
126 */
127 if (((d_l2_conn->diag_l2_type & DIAG_L2_TYPE_INITMASK) ==DIAG_L2_TYPE_MONINIT) ||
128 (d_l2_conn->diag_l2_state != DIAG_L2_STATE_OPEN) ||
129 (d_l2_conn->diag_link->l1flags & DIAG_L1_DOESKEEPALIVE)) {
130 continue;
131 }
132
133 /* Check the send timers vs requested expiry time */
134
135 //we're subtracting unsigned values but since the clock is
136 //monotonic, the difference will always be >= 0
137 expired = ((now - d_l2_conn->tlast) > d_l2_conn->tinterval)? 1:0;
138
139 /* If expired, call the timeout routine */
140 if (expired && d_l2_conn->l2proto->diag_l2_proto_timeout) {
141 d_l2_conn->l2proto->diag_l2_proto_timeout(d_l2_conn);
142 }
143 }
144 diag_os_unlock(&l2internal.connlist_mtx);
145 return;
146}
147
148/*
149 * Add a message to the message list on the L2 connection
150 * (if msg was a chain of messages, they all get added so they don't get lost)
151 */
152void diag_l2_addmsg(struct diag_l2_conn *d_l2_conn, struct diag_msg *msg) {
153 LL_CONCAT(d_l2_conn->diag_msg, msg);
154 return;
155}
156
157/************************************************************************/
158/* PUBLIC Interface starts here */
159/************************************************************************/
160
161/*
162 * Init called to initialise local structures
163 */
164int diag_l2_init() {
165
166 if (l2internal.init_done) {
167 return 0;
168 }
169
170 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_INIT, DIAG_DBGLEVEL_V,
171 FLFMT "entered diag_l2_init\n", FL);
172
173 diag_os_initstaticmtx(&l2internal.connlist_mtx);
174
175 l2internal.dl2l_list = NULL;
176 l2internal.dl2conn_list = NULL;
177
178 l2internal.init_done = 1;
179 return 0;
180}
181
182//diag_l2_end : opposite of diag_l2_init !
183int diag_l2_end() {
184 diag_os_delmtx(&l2internal.connlist_mtx);
185 l2internal.init_done = 0;
186 return 0;
187}
188
189/** Remove a dl2l from the linked list,
190 * close its dl0d link with diag_l1_close and free
191 * the dl2l.
192 * This should only be called if we're sure the dl2l
193 * is not used anymore...
194 */
195static int diag_l2_closelink(struct diag_l2_link *dl2l) {
196 assert(dl2l != NULL);
197
198 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_CLOSE, DIAG_DBGLEVEL_V,
199 FLFMT "l2_closelink %p called\n", FL, (void *)dl2l);
200
201 /* Remove from linked-list */
202 LL_DELETE(l2internal.dl2l_list, dl2l);
203
204 if (dl2l->l2_dl0d == NULL) {
205 fprintf(stderr, FLFMT "**** Corrupt DL2L !! Report this !!!\n", FL);
206 } else {
207 diag_l1_close(dl2l->l2_dl0d);
208 }
209
210 free(dl2l);
211
212 return 0;
213}
214
215/*
216 * Open an L2 link over specified diag_l0_device.
217 * Aborts if the L1 protocol doesn't match.
218 * Ret 0 if ok
219 *
220 * We need to specify the L1 protocol, because some L1
221 * interfaces are smart and can support multiple protocols, also L2 needs
222 * to know later (and asks L1)
223 */
224int diag_l2_open(struct diag_l0_device *dl0d, int L1protocol) {
225 int rv;
226 struct diag_l2_link *dl2l;
227
228 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_OPEN, DIAG_DBGLEVEL_V,
229 FLFMT "l2_open %s on %p, L1proto=%d\n",
230 FL, dl0d->dl0->longname, (void *)dl0d, L1protocol);
231
232 /* try to find in linked list */
233 dl2l = diag_l2_findlink(dl0d);
234
235 if (dl2l) {
236 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_OPEN, DIAG_DBGLEVEL_V,
237 "\texisting L2 link \"%s\" found\n",
238 dl2l->l2_dl0d->dl0->shortname);
239
240 if (dl2l->l1proto != L1protocol) {
241 fprintf(stderr, "Problem : L0 open with wrong L1 proto...\n");
242 return diag_iseterr(DIAG_ERR_PROTO_NOTSUPP);
243 }
244
245 /* Device was already open, with correct protocol */
246 return 0;
247 }
248
249 rv = diag_l1_open(dl0d, L1protocol);
250 if (rv) {
251 return diag_ifwderr(rv); //forward error to next level
252 }
253
254 /* Create the L2 link */
255 if ((rv=diag_calloc(&dl2l, 1))) {
256 return diag_ifwderr(rv);
257 }
258
259 dl2l->l2_dl0d = dl0d;
260 dl2l->l1flags = diag_l1_getflags(dl0d);
261 dl2l->l1type = diag_l1_gettype(dl0d);
262 dl2l->l1proto = L1protocol;
263
264 /* Put ourselves at the head of the list. */
265 LL_PREPEND(l2internal.dl2l_list, dl2l);
266
267 return 0;
268}
269
270/*
271 * Close a L2 interface, the caller
272 * must have closed all the L3 connections relating to this or they will
273 * just get left hanging using resources.
274 * XXX what do we want to accomplish here ?
275 * We can't have multiple L2 links with the same diag_l0_device, because
276 * of how diag_l2_open and diag_l2_findlink work.
277 * This func will probably need to be modified if we want to do fancy
278 * multi-protocol / multi-L2 stuff...
279 *
280 * Currently this checks if there's a dl2_link using that dl0d.
281 * If there isn't, we close it.
282 * If there's a dl2 link, we check if it's still used by parsing
283 * through all the diag_l2_conns.
284 */
285int diag_l2_close(struct diag_l0_device *dl0d) {
286 struct diag_l2_conn *d_l2_conn;
287 struct diag_l2_link *dl2l;
288
289 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_CLOSE, DIAG_DBGLEVEL_V,
290 FLFMT "Entered diag_l2_close for dl0d=%p;\n",
291 FL, (void *)dl0d);
292
293 assert(dl0d !=NULL); //crash if it's null. We need to fix these problems.
294
295 diag_os_lock(&l2internal.connlist_mtx);
296
297 // Check if dl0d is still used by someone in l2internal.dl2conn_list
298 LL_FOREACH(l2internal.dl2conn_list, d_l2_conn) {
299 if (d_l2_conn->diag_link->l2_dl0d == dl0d) {
300 fprintf(stderr, FLFMT "Not closing dl0d: used by dl2conn %p!\n", FL,
301 (void *) d_l2_conn);
302 diag_os_unlock(&l2internal.connlist_mtx);
303 return diag_iseterr(DIAG_ERR_GENERAL); //there's still a dl2conn using it !
304 }
305 }
306
307 while ((dl2l = diag_l2_findlink(dl0d)) != NULL) {
308 /* can't just "LL_FOREACH" since we're removing stuff from the list as we go */
309 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_CLOSE, DIAG_DBGLEVEL_V,
310 "\tclosing dl2link %p.\n", (void *) dl2l);
311 diag_l2_closelink(dl2l); //closelink calls diag_l1_close() as required
312 }
313
314 diag_os_unlock(&l2internal.connlist_mtx);
315 return 0;
316}
317
318/*
319 * startCommunication routine, establishes a connection to
320 * an ECU by sending fast/slow start (or whatever the protocol is),
321 * and sets all the timer parameters etc etc
322 * this creates & alloc's a new diag_l2_conn (freed in diag_l2_stopcomm)
323 * we just pass along the flags arg straight to the proto_startcomm()
324 * L2protocol : matched with ->diag_l2_protocol constants
325 */
326struct diag_l2_conn *diag_l2_StartCommunications(struct diag_l0_device *dl0d, int L2protocol, flag_type flags,
327 unsigned int bitrate, target_type target, source_type source) {
328 struct diag_l2_conn *d_l2_conn;
329 const struct diag_l2_proto *dl2p;
330
331 struct diag_l2_link *dl2l;
332 int i,rv;
333
334 assert(dl0d!=NULL);
335
336 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_OPEN, DIAG_DBGLEVEL_V,
337 FLFMT "_startCommunications dl0d=%p L2proto %d flags=0x%X "
338 "%ubps target=0x%X src=0x%X\n",
339 FL, (void *)dl0d, L2protocol, flags, bitrate,
340 target & 0xff, source & 0xff);
341
342 /* there must be a dl2l with the desired dl0d. */
343 dl2l = diag_l2_findlink(dl0d);
344 if (!dl2l) {
345 fprintf(stderr, "No dl2l with requested dl0 !?\n");
346 return diag_pseterr(DIAG_ERR_GENERAL);
347 }
348
349 /*
350 * Check connection doesn't exist already, if it does, do not reuse !
351 * with the current L1/L2 structure, hoping to share one L1 between more than one l2
352 * is a bad idea.
353 */
354
355 diag_os_lock(&l2internal.connlist_mtx);
356
357 LL_FOREACH(l2internal.dl2conn_list, d_l2_conn) {
358 if (d_l2_conn->diag_link == dl2l) {
359 fprintf(stderr, "Already an L2 connection with specified dl0-dl2l, cannot reuse !\n");
360 diag_os_unlock(&l2internal.connlist_mtx);
361 return diag_pseterr(DIAG_ERR_GENERAL);
362 }
363 }
364
365 /* Create new L2 connection */
366 rv = diag_calloc(&d_l2_conn, 1);
367 if (rv != 0) {
368 diag_os_unlock(&l2internal.connlist_mtx);
369 return diag_pfwderr(rv);
370 }
371 d_l2_conn->diag_link = dl2l;
372
373 /* Look up the protocol we want to use */
374
375 d_l2_conn->l2proto = NULL;
376
377 for (i=0; l2proto_list[i] ; i++) {
378 dl2p = l2proto_list[i];
379 if (dl2p->diag_l2_protocol == L2protocol) {
380 d_l2_conn->l2proto = dl2p;
381 break;
382 }
383 }
384
385 if (d_l2_conn->l2proto == NULL) {
386 fprintf(stderr,
387 FLFMT "Protocol %d not installed.\n", FL, L2protocol);
388 free(d_l2_conn);
389 diag_os_unlock(&l2internal.connlist_mtx);
390 return diag_pseterr(DIAG_ERR_GENERAL);
391 }
392
393
394 d_l2_conn->diag_l2_type = flags;
395 d_l2_conn->diag_l2_srcaddr = source;
396 d_l2_conn->diag_l2_destaddr = target;
397
398 /*
399 * We are going to assume that the ISO default timing values
400 * are general suitable defaults
401 */
402 d_l2_conn->diag_l2_p1min = ISO_14230_TIM_MIN_P1;
403 d_l2_conn->diag_l2_p1max = ISO_14230_TIM_MAX_P1;
404 d_l2_conn->diag_l2_p2min = ISO_14230_TIM_MIN_P2;
405 d_l2_conn->diag_l2_p2max = ISO_14230_TIM_MAX_P2;
406 d_l2_conn->diag_l2_p2emin = ISO_14230_TIM_MIN_P2E;
407 d_l2_conn->diag_l2_p2emax = ISO_14230_TIM_MAX_P2E;
408 d_l2_conn->diag_l2_p3min = ISO_14230_TIM_MIN_P3;
409 d_l2_conn->diag_l2_p3max = ISO_14230_TIM_MAX_P3;
410 d_l2_conn->diag_l2_p4min = ISO_14230_TIM_MIN_P4;
411 d_l2_conn->diag_l2_p4max = ISO_14230_TIM_MAX_P4;
412
413 d_l2_conn->tinterval = (ISO_14230_TIM_MAX_P3 * 2/3); //default keepalive interval.
414
415 d_l2_conn->diag_l2_state = DIAG_L2_STATE_CLOSED;
416
417 /* Now do protocol version of StartCommunications */
418
419 rv = d_l2_conn->l2proto->diag_l2_proto_startcomms(d_l2_conn,
420 flags, bitrate, target, source);
421
422 if (rv < 0) {
423 /* Something went wrong */
424 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_OPEN, DIAG_DBGLEVEL_V,
425 FLFMT "protocol startcomms returned %d\n", FL, rv);
426
427 free(d_l2_conn);
428 diag_os_unlock(&l2internal.connlist_mtx);
429 return diag_pfwderr(rv);
430 }
431
432 /*
433 * note target for quick lookup of connection for received messages
434 * - unless we're re-using a established connection, then no need
435 * to re-note.
436 */
437
438 /* And attach connection info to our main list */
439 LL_PREPEND(l2internal.dl2conn_list, d_l2_conn);
440
441 d_l2_conn->tlast=diag_os_getms();
442 d_l2_conn->diag_l2_state = DIAG_L2_STATE_OPEN;
443
444 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_OPEN, DIAG_DBGLEVEL_V,
445 FLFMT "diag_l2_StartComms returns %p\n",
446 FL, (void *)d_l2_conn);
447
448 diag_os_unlock(&l2internal.connlist_mtx);
449 return d_l2_conn;
450}
451
452/*
453 * Stop communications - stop talking to an ECU
454 * - some L2 protocols have an ordered mechanism to do this, others are
455 * just timeout based (i.e don't send anything for 5 seconds)
456 * this also free()s d_l2_conn (alloced in startcomm)
457 * and removes it from l2internal.dl2conn_list
458 */
459int diag_l2_StopCommunications(struct diag_l2_conn *d_l2_conn) {
460 assert(d_l2_conn != NULL);
461
462 d_l2_conn->diag_l2_state = DIAG_L2_STATE_CLOSING;
463
464 /*
465 * Call protocol close routine, if it exists
466 */
467 if (d_l2_conn->l2proto->diag_l2_proto_stopcomms) {
468 (void)d_l2_conn->l2proto->diag_l2_proto_stopcomms(d_l2_conn);
469 }
470
471 //remove from the main linked list
472 diag_l2_rmconn(d_l2_conn);
473
474 //We assume the protocol-specific _stopcomms() cleared out anything it
475 //may have alloc'ed. inside the l2 connection struct.
476 // But we might still have some attached messages that
477 //were never freed, so we need to purge those:
478 if (d_l2_conn->diag_msg != NULL) {
479 diag_freemsg(d_l2_conn->diag_msg);
480 }
481
482 //and free() the connection.
483 free(d_l2_conn);
484
485 return 0;
486}
487
488
489/*
490 * Send a message. This is synchronous.
491 * calls the appropriate l2_proto_send()
492 * and updates the timestamps
493 */
494int diag_l2_send(struct diag_l2_conn *d_l2_conn, struct diag_msg *msg) {
495 int rv;
496
497 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_WRITE, DIAG_DBGLEVEL_V,
498 FLFMT "diag_l2_send %p msg %p msglen %u called\n",
499 FL, (void *)d_l2_conn, (void *)msg, msg->len);
500
501 /* Call protocol specific send routine */
502 rv = d_l2_conn->l2proto->diag_l2_proto_send(d_l2_conn, msg);
503
504 if (rv==0) {
505 //update timestamp
506 d_l2_conn->tlast = diag_os_getms();
507 }
508
509
510 return rv? diag_ifwderr(rv):0;
511}
512
513/*
514 * Send a message, and wait the appropriate time for a response and return
515 * that message or an error indicator
516 * This is synchronous and sleeps and is meant too.
517 */
518struct diag_msg *diag_l2_request(struct diag_l2_conn *d_l2_conn, struct diag_msg *msg, int *errval) {
519 struct diag_msg *rxmsg;
520
521 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_WRITE, DIAG_DBGLEVEL_V,
522 FLFMT "_request dl2c=%p msg=%p called\n",
523 FL, (void *)d_l2_conn, (void *)msg);
524
525 /* Call protocol specific send routine */
526 rxmsg = d_l2_conn->l2proto->diag_l2_proto_request(d_l2_conn, msg, errval);
527
528 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_WRITE, DIAG_DBGLEVEL_V,
529 FLFMT "_request returns %p, err %d\n",
530 FL, (void *)rxmsg, *errval);
531
532 if (rxmsg==NULL) {
533 return diag_pfwderr(*errval);
534 }
535 //update timers
536 d_l2_conn->tlast = diag_os_getms();
537
538 return rxmsg;
539}
540
541
542/*
543 * Recv a message - will end up calling the callback routine with a message
544 * or an error if an error has occurred
545 *
546 * At the moment this sleeps and the callback will happen before the recv()
547 * returns - this is not the intention XXX we need to clarify this
548 *
549 * Timeout is in ms
550 */
551int diag_l2_recv(struct diag_l2_conn *d_l2_conn, unsigned int timeout,
552 void (*callback)(void *handle, struct diag_msg *msg), void *handle) {
553 int rv;
554
555 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_READ, DIAG_DBGLEVEL_V,
556 FLFMT "diag_l2_recv %p timeout %u called\n",
557 FL, (void *)d_l2_conn, timeout);
558
559 /* Call protocol specific recv routine */
560 rv = d_l2_conn->l2proto->diag_l2_proto_recv(d_l2_conn, timeout, callback, handle);
561
562 if (rv==0) {
563 //update timers if success
564 d_l2_conn->tlast = diag_os_getms();
565 } else {
566 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_READ, DIAG_DBGLEVEL_V,
567 FLFMT "diag_l2_recv returns %d\n", FL, rv);
568 }
569
570 return rv;
571}
572
573/*
574 * IOCTL, for setting/asking how various layers are working - similar to
575 * Unix ioctl()
576 * ret 0 if ok
577 */
578int diag_l2_ioctl(struct diag_l2_conn *d_l2_conn, unsigned int cmd, void *data) {
579 struct diag_l0_device *dl0d;
580 int rv = 0;
581 struct diag_l2_data *d;
582 struct diag_l2_link *dl2l;
583
584 DIAG_DBGM(diag_l2_debug, DIAG_DEBUG_IOCTL, DIAG_DBGLEVEL_V,
585 FLFMT "diag_l2_ioctl %p cmd 0x%X\n",
586 FL, (void *)d_l2_conn, cmd);
587
588
589 dl2l = d_l2_conn->diag_link;
590 dl0d = dl2l->l2_dl0d;
591
592 switch (cmd) {
593 case DIAG_IOCTL_GET_L1_TYPE:
594 *(int *)data = diag_l1_gettype(dl0d);
595 break;
596 case DIAG_IOCTL_GET_L1_FLAGS:
597 *(uint32_t *)data = diag_l1_getflags(dl0d);
598 break;
599 case DIAG_IOCTL_GET_L2_FLAGS:
600 *(int *)data = d_l2_conn->l2proto->diag_l2_flags;
601 break;
602 case DIAG_IOCTL_GET_L2_DATA:
603 d = (struct diag_l2_data *)data;
604 d->physaddr = d_l2_conn->diag_l2_physaddr;
605 d->kb1 = d_l2_conn->diag_l2_kb1;
606 d->kb2 = d_l2_conn->diag_l2_kb2;
607 break;
608 case DIAG_IOCTL_SETSPEED:
609 if (dl2l->l1flags & (DIAG_L1_AUTOSPEED | DIAG_L1_NOTTY)) {
610 break;
611 }
612 rv = diag_l1_ioctl(dl0d, cmd, data);
613 break;
614 case DIAG_IOCTL_IFLUSH:
615 if (dl2l->l1flags & DIAG_L1_NOTTY) {
616 break;
617 }
618 rv = diag_l1_ioctl(dl0d, cmd, data);
619 break;
620 case DIAG_IOCTL_SETWM:
621 if (!(dl2l->l1flags & DIAG_L1_DOESKEEPALIVE)) {
622 break;
623 }
624 rv = diag_l1_ioctl(dl0d, cmd, data);
625 break;
626 case DIAG_IOCTL_INITBUS:
627 //fall-through to L1
628 default:
629 /* Not implemented by L2 : forward to L1 */
630 rv = diag_l1_ioctl(dl0d, cmd, data);
631 break;
632 }
633
634 return rv? diag_ifwderr(rv):0;
635}
636