xref: /linux/drivers/usb/host/uhci-debug.c (revision 2da68a77)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * UHCI-specific debugging code. Invaluable when something
4  * goes wrong, but don't get in my face.
5  *
6  * Kernel visible pointers are surrounded in []s and bus
7  * visible pointers are surrounded in ()s
8  *
9  * (C) Copyright 1999 Linus Torvalds
10  * (C) Copyright 1999-2001 Johannes Erdfelt
11  */
12 
13 #include <linux/slab.h>
14 #include <linux/kernel.h>
15 #include <linux/debugfs.h>
16 #include <asm/io.h>
17 
18 #include "uhci-hcd.h"
19 
20 #define EXTRA_SPACE	1024
21 
22 static struct dentry *uhci_debugfs_root;
23 
24 #ifdef CONFIG_DYNAMIC_DEBUG
25 
26 /* Handle REALLY large printks so we don't overflow buffers */
27 static void lprintk(char *buf)
28 {
29 	char *p;
30 
31 	/* Just write one line at a time */
32 	while (buf) {
33 		p = strchr(buf, '\n');
34 		if (p)
35 			*p = 0;
36 		printk(KERN_DEBUG "%s\n", buf);
37 		buf = p;
38 		if (buf)
39 			buf++;
40 	}
41 }
42 
43 static int uhci_show_td(struct uhci_hcd *uhci, struct uhci_td *td, char *buf,
44 			int len, int space)
45 {
46 	char *out = buf;
47 	char *spid;
48 	u32 status, token;
49 
50 	status = td_status(uhci, td);
51 	out += sprintf(out, "%*s[%p] link (%08x) ", space, "", td,
52 		hc32_to_cpu(uhci, td->link));
53 	out += sprintf(out, "e%d %s%s%s%s%s%s%s%s%s%sLength=%x ",
54 		((status >> 27) & 3),
55 		(status & TD_CTRL_SPD) ?      "SPD " : "",
56 		(status & TD_CTRL_LS) ?       "LS " : "",
57 		(status & TD_CTRL_IOC) ?      "IOC " : "",
58 		(status & TD_CTRL_ACTIVE) ?   "Active " : "",
59 		(status & TD_CTRL_STALLED) ?  "Stalled " : "",
60 		(status & TD_CTRL_DBUFERR) ?  "DataBufErr " : "",
61 		(status & TD_CTRL_BABBLE) ?   "Babble " : "",
62 		(status & TD_CTRL_NAK) ?      "NAK " : "",
63 		(status & TD_CTRL_CRCTIMEO) ? "CRC/Timeo " : "",
64 		(status & TD_CTRL_BITSTUFF) ? "BitStuff " : "",
65 		status & 0x7ff);
66 	if (out - buf > len)
67 		goto done;
68 
69 	token = td_token(uhci, td);
70 	switch (uhci_packetid(token)) {
71 		case USB_PID_SETUP:
72 			spid = "SETUP";
73 			break;
74 		case USB_PID_OUT:
75 			spid = "OUT";
76 			break;
77 		case USB_PID_IN:
78 			spid = "IN";
79 			break;
80 		default:
81 			spid = "?";
82 			break;
83 	}
84 
85 	out += sprintf(out, "MaxLen=%x DT%d EndPt=%x Dev=%x, PID=%x(%s) ",
86 		token >> 21,
87 		((token >> 19) & 1),
88 		(token >> 15) & 15,
89 		(token >> 8) & 127,
90 		(token & 0xff),
91 		spid);
92 	out += sprintf(out, "(buf=%08x)\n", hc32_to_cpu(uhci, td->buffer));
93 
94 done:
95 	if (out - buf > len)
96 		out += sprintf(out, " ...\n");
97 	return out - buf;
98 }
99 
100 static int uhci_show_urbp(struct uhci_hcd *uhci, struct urb_priv *urbp,
101 			char *buf, int len, int space)
102 {
103 	char *out = buf;
104 	struct uhci_td *td;
105 	int i, nactive, ninactive;
106 	char *ptype;
107 
108 
109 	out += sprintf(out, "urb_priv [%p] ", urbp);
110 	out += sprintf(out, "urb [%p] ", urbp->urb);
111 	out += sprintf(out, "qh [%p] ", urbp->qh);
112 	out += sprintf(out, "Dev=%d ", usb_pipedevice(urbp->urb->pipe));
113 	out += sprintf(out, "EP=%x(%s) ", usb_pipeendpoint(urbp->urb->pipe),
114 			(usb_pipein(urbp->urb->pipe) ? "IN" : "OUT"));
115 	if (out - buf > len)
116 		goto done;
117 
118 	switch (usb_pipetype(urbp->urb->pipe)) {
119 	case PIPE_ISOCHRONOUS: ptype = "ISO"; break;
120 	case PIPE_INTERRUPT: ptype = "INT"; break;
121 	case PIPE_BULK: ptype = "BLK"; break;
122 	default:
123 	case PIPE_CONTROL: ptype = "CTL"; break;
124 	}
125 
126 	out += sprintf(out, "%s%s", ptype, (urbp->fsbr ? " FSBR" : ""));
127 	out += sprintf(out, " Actlen=%d%s", urbp->urb->actual_length,
128 			(urbp->qh->type == USB_ENDPOINT_XFER_CONTROL ?
129 				"-8" : ""));
130 
131 	if (urbp->urb->unlinked)
132 		out += sprintf(out, " Unlinked=%d", urbp->urb->unlinked);
133 	out += sprintf(out, "\n");
134 
135 	if (out - buf > len)
136 		goto done;
137 
138 	i = nactive = ninactive = 0;
139 	list_for_each_entry(td, &urbp->td_list, list) {
140 		if (urbp->qh->type != USB_ENDPOINT_XFER_ISOC &&
141 				(++i <= 10 || debug > 2)) {
142 			out += sprintf(out, "%*s%d: ", space + 2, "", i);
143 			out += uhci_show_td(uhci, td, out,
144 					len - (out - buf), 0);
145 			if (out - buf > len)
146 				goto tail;
147 		} else {
148 			if (td_status(uhci, td) & TD_CTRL_ACTIVE)
149 				++nactive;
150 			else
151 				++ninactive;
152 		}
153 	}
154 	if (nactive + ninactive > 0)
155 		out += sprintf(out,
156 				"%*s[skipped %d inactive and %d active TDs]\n",
157 				space, "", ninactive, nactive);
158 done:
159 	if (out - buf > len)
160 		out += sprintf(out, " ...\n");
161 tail:
162 	return out - buf;
163 }
164 
165 static int uhci_show_qh(struct uhci_hcd *uhci,
166 		struct uhci_qh *qh, char *buf, int len, int space)
167 {
168 	char *out = buf;
169 	int i, nurbs;
170 	__hc32 element = qh_element(qh);
171 	char *qtype;
172 
173 	switch (qh->type) {
174 	case USB_ENDPOINT_XFER_ISOC: qtype = "ISO"; break;
175 	case USB_ENDPOINT_XFER_INT: qtype = "INT"; break;
176 	case USB_ENDPOINT_XFER_BULK: qtype = "BLK"; break;
177 	case USB_ENDPOINT_XFER_CONTROL: qtype = "CTL"; break;
178 	default: qtype = "Skel" ; break;
179 	}
180 
181 	out += sprintf(out, "%*s[%p] %s QH link (%08x) element (%08x)\n",
182 			space, "", qh, qtype,
183 			hc32_to_cpu(uhci, qh->link),
184 			hc32_to_cpu(uhci, element));
185 	if (qh->type == USB_ENDPOINT_XFER_ISOC)
186 		out += sprintf(out,
187 				"%*s    period %d phase %d load %d us, frame %x desc [%p]\n",
188 				space, "", qh->period, qh->phase, qh->load,
189 				qh->iso_frame, qh->iso_packet_desc);
190 	else if (qh->type == USB_ENDPOINT_XFER_INT)
191 		out += sprintf(out, "%*s    period %d phase %d load %d us\n",
192 				space, "", qh->period, qh->phase, qh->load);
193 	if (out - buf > len)
194 		goto done;
195 
196 	if (element & UHCI_PTR_QH(uhci))
197 		out += sprintf(out, "%*s  Element points to QH (bug?)\n", space, "");
198 
199 	if (element & UHCI_PTR_DEPTH(uhci))
200 		out += sprintf(out, "%*s  Depth traverse\n", space, "");
201 
202 	if (element & cpu_to_hc32(uhci, 8))
203 		out += sprintf(out, "%*s  Bit 3 set (bug?)\n", space, "");
204 
205 	if (!(element & ~(UHCI_PTR_QH(uhci) | UHCI_PTR_DEPTH(uhci))))
206 		out += sprintf(out, "%*s  Element is NULL (bug?)\n", space, "");
207 
208 	if (out - buf > len)
209 		goto done;
210 
211 	if (list_empty(&qh->queue)) {
212 		out += sprintf(out, "%*s  queue is empty\n", space, "");
213 		if (qh == uhci->skel_async_qh) {
214 			out += uhci_show_td(uhci, uhci->term_td, out,
215 					len - (out - buf), 0);
216 			if (out - buf > len)
217 				goto tail;
218 		}
219 	} else {
220 		struct urb_priv *urbp = list_entry(qh->queue.next,
221 				struct urb_priv, node);
222 		struct uhci_td *td = list_entry(urbp->td_list.next,
223 				struct uhci_td, list);
224 
225 		if (element != LINK_TO_TD(uhci, td))
226 			out += sprintf(out, "%*s Element != First TD\n",
227 					space, "");
228 		i = nurbs = 0;
229 		list_for_each_entry(urbp, &qh->queue, node) {
230 			if (++i <= 10) {
231 				out += uhci_show_urbp(uhci, urbp, out,
232 						len - (out - buf), space + 2);
233 				if (out - buf > len)
234 					goto tail;
235 			}
236 			else
237 				++nurbs;
238 		}
239 		if (nurbs > 0)
240 			out += sprintf(out, "%*s Skipped %d URBs\n",
241 					space, "", nurbs);
242 	}
243 
244 	if (out - buf > len)
245 		goto done;
246 
247 	if (qh->dummy_td) {
248 		out += sprintf(out, "%*s  Dummy TD\n", space, "");
249 		out += uhci_show_td(uhci, qh->dummy_td, out,
250 				len - (out - buf), 0);
251 		if (out - buf > len)
252 			goto tail;
253 	}
254 
255 done:
256 	if (out - buf > len)
257 		out += sprintf(out, " ...\n");
258 tail:
259 	return out - buf;
260 }
261 
262 static int uhci_show_sc(int port, unsigned short status, char *buf)
263 {
264 	return sprintf(buf, "  stat%d     =     %04x  %s%s%s%s%s%s%s%s%s%s\n",
265 		port,
266 		status,
267 		(status & USBPORTSC_SUSP) ?	" Suspend" : "",
268 		(status & USBPORTSC_OCC) ?	" OverCurrentChange" : "",
269 		(status & USBPORTSC_OC) ?	" OverCurrent" : "",
270 		(status & USBPORTSC_PR) ?	" Reset" : "",
271 		(status & USBPORTSC_LSDA) ?	" LowSpeed" : "",
272 		(status & USBPORTSC_RD) ?	" ResumeDetect" : "",
273 		(status & USBPORTSC_PEC) ?	" EnableChange" : "",
274 		(status & USBPORTSC_PE) ?	" Enabled" : "",
275 		(status & USBPORTSC_CSC) ?	" ConnectChange" : "",
276 		(status & USBPORTSC_CCS) ?	" Connected" : "");
277 }
278 
279 static int uhci_show_root_hub_state(struct uhci_hcd *uhci, char *buf)
280 {
281 	char *rh_state;
282 
283 	switch (uhci->rh_state) {
284 	    case UHCI_RH_RESET:
285 		rh_state = "reset";		break;
286 	    case UHCI_RH_SUSPENDED:
287 		rh_state = "suspended";		break;
288 	    case UHCI_RH_AUTO_STOPPED:
289 		rh_state = "auto-stopped";	break;
290 	    case UHCI_RH_RESUMING:
291 		rh_state = "resuming";		break;
292 	    case UHCI_RH_SUSPENDING:
293 		rh_state = "suspending";	break;
294 	    case UHCI_RH_RUNNING:
295 		rh_state = "running";		break;
296 	    case UHCI_RH_RUNNING_NODEVS:
297 		rh_state = "running, no devs";	break;
298 	    default:
299 		rh_state = "?";			break;
300 	}
301 	return sprintf(buf, "Root-hub state: %s   FSBR: %d\n",
302 			rh_state, uhci->fsbr_is_on);
303 }
304 
305 static int uhci_show_status(struct uhci_hcd *uhci, char *buf, int len)
306 {
307 	char *out = buf;
308 	unsigned short usbcmd, usbstat, usbint, usbfrnum;
309 	unsigned int flbaseadd;
310 	unsigned char sof;
311 	unsigned short portsc1, portsc2;
312 
313 
314 	usbcmd    = uhci_readw(uhci, USBCMD);
315 	usbstat   = uhci_readw(uhci, USBSTS);
316 	usbint    = uhci_readw(uhci, USBINTR);
317 	usbfrnum  = uhci_readw(uhci, USBFRNUM);
318 	flbaseadd = uhci_readl(uhci, USBFLBASEADD);
319 	sof       = uhci_readb(uhci, USBSOF);
320 	portsc1   = uhci_readw(uhci, USBPORTSC1);
321 	portsc2   = uhci_readw(uhci, USBPORTSC2);
322 
323 	out += sprintf(out, "  usbcmd    =     %04x   %s%s%s%s%s%s%s%s\n",
324 		usbcmd,
325 		(usbcmd & USBCMD_MAXP) ?    "Maxp64 " : "Maxp32 ",
326 		(usbcmd & USBCMD_CF) ?      "CF " : "",
327 		(usbcmd & USBCMD_SWDBG) ?   "SWDBG " : "",
328 		(usbcmd & USBCMD_FGR) ?     "FGR " : "",
329 		(usbcmd & USBCMD_EGSM) ?    "EGSM " : "",
330 		(usbcmd & USBCMD_GRESET) ?  "GRESET " : "",
331 		(usbcmd & USBCMD_HCRESET) ? "HCRESET " : "",
332 		(usbcmd & USBCMD_RS) ?      "RS " : "");
333 	if (out - buf > len)
334 		goto done;
335 
336 	out += sprintf(out, "  usbstat   =     %04x   %s%s%s%s%s%s\n",
337 		usbstat,
338 		(usbstat & USBSTS_HCH) ?    "HCHalted " : "",
339 		(usbstat & USBSTS_HCPE) ?   "HostControllerProcessError " : "",
340 		(usbstat & USBSTS_HSE) ?    "HostSystemError " : "",
341 		(usbstat & USBSTS_RD) ?     "ResumeDetect " : "",
342 		(usbstat & USBSTS_ERROR) ?  "USBError " : "",
343 		(usbstat & USBSTS_USBINT) ? "USBINT " : "");
344 	if (out - buf > len)
345 		goto done;
346 
347 	out += sprintf(out, "  usbint    =     %04x\n", usbint);
348 	out += sprintf(out, "  usbfrnum  =   (%d)%03x\n", (usbfrnum >> 10) & 1,
349 		0xfff & (4*(unsigned int)usbfrnum));
350 	out += sprintf(out, "  flbaseadd = %08x\n", flbaseadd);
351 	out += sprintf(out, "  sof       =       %02x\n", sof);
352 	if (out - buf > len)
353 		goto done;
354 
355 	out += uhci_show_sc(1, portsc1, out);
356 	if (out - buf > len)
357 		goto done;
358 
359 	out += uhci_show_sc(2, portsc2, out);
360 	if (out - buf > len)
361 		goto done;
362 
363 	out += sprintf(out,
364 			"Most recent frame: %x (%d)   Last ISO frame: %x (%d)\n",
365 			uhci->frame_number, uhci->frame_number & 1023,
366 			uhci->last_iso_frame, uhci->last_iso_frame & 1023);
367 
368 done:
369 	if (out - buf > len)
370 		out += sprintf(out, " ...\n");
371 	return out - buf;
372 }
373 
374 static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
375 {
376 	char *out = buf;
377 	int i, j;
378 	struct uhci_qh *qh;
379 	struct uhci_td *td;
380 	struct list_head *tmp, *head;
381 	int nframes, nerrs;
382 	__hc32 link;
383 	__hc32 fsbr_link;
384 
385 	static const char * const qh_names[] = {
386 		"unlink", "iso", "int128", "int64", "int32", "int16",
387 		"int8", "int4", "int2", "async", "term"
388 	};
389 
390 	out += uhci_show_root_hub_state(uhci, out);
391 	if (out - buf > len)
392 		goto done;
393 	out += sprintf(out, "HC status\n");
394 	out += uhci_show_status(uhci, out, len - (out - buf));
395 	if (out - buf > len)
396 		goto tail;
397 
398 	out += sprintf(out, "Periodic load table\n");
399 	for (i = 0; i < MAX_PHASE; ++i) {
400 		out += sprintf(out, "\t%d", uhci->load[i]);
401 		if (i % 8 == 7)
402 			*out++ = '\n';
403 	}
404 	out += sprintf(out, "Total: %d, #INT: %d, #ISO: %d\n",
405 			uhci->total_load,
406 			uhci_to_hcd(uhci)->self.bandwidth_int_reqs,
407 			uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs);
408 	if (debug <= 1)
409 		goto tail;
410 
411 	out += sprintf(out, "Frame List\n");
412 	nframes = 10;
413 	nerrs = 0;
414 	for (i = 0; i < UHCI_NUMFRAMES; ++i) {
415 		__hc32 qh_dma;
416 
417 		if (out - buf > len)
418 			goto done;
419 		j = 0;
420 		td = uhci->frame_cpu[i];
421 		link = uhci->frame[i];
422 		if (!td)
423 			goto check_link;
424 
425 		if (nframes > 0) {
426 			out += sprintf(out, "- Frame %d -> (%08x)\n",
427 					i, hc32_to_cpu(uhci, link));
428 			j = 1;
429 		}
430 
431 		head = &td->fl_list;
432 		tmp = head;
433 		do {
434 			td = list_entry(tmp, struct uhci_td, fl_list);
435 			tmp = tmp->next;
436 			if (link != LINK_TO_TD(uhci, td)) {
437 				if (nframes > 0) {
438 					out += sprintf(out,
439 						"    link does not match list entry!\n");
440 					if (out - buf > len)
441 						goto done;
442 				} else
443 					++nerrs;
444 			}
445 			if (nframes > 0) {
446 				out += uhci_show_td(uhci, td, out,
447 						len - (out - buf), 4);
448 				if (out - buf > len)
449 					goto tail;
450 			}
451 			link = td->link;
452 		} while (tmp != head);
453 
454 check_link:
455 		qh_dma = uhci_frame_skel_link(uhci, i);
456 		if (link != qh_dma) {
457 			if (nframes > 0) {
458 				if (!j) {
459 					out += sprintf(out,
460 						"- Frame %d -> (%08x)\n",
461 						i, hc32_to_cpu(uhci, link));
462 					j = 1;
463 				}
464 				out += sprintf(out,
465 					"   link does not match QH (%08x)!\n",
466 					hc32_to_cpu(uhci, qh_dma));
467 				if (out - buf > len)
468 					goto done;
469 			} else
470 				++nerrs;
471 		}
472 		nframes -= j;
473 	}
474 	if (nerrs > 0)
475 		out += sprintf(out, "Skipped %d bad links\n", nerrs);
476 
477 	out += sprintf(out, "Skeleton QHs\n");
478 
479 	if (out - buf > len)
480 		goto done;
481 
482 	fsbr_link = 0;
483 	for (i = 0; i < UHCI_NUM_SKELQH; ++i) {
484 		int cnt = 0;
485 
486 		qh = uhci->skelqh[i];
487 		out += sprintf(out, "- skel_%s_qh\n", qh_names[i]);
488 		out += uhci_show_qh(uhci, qh, out, len - (out - buf), 4);
489 		if (out - buf > len)
490 			goto tail;
491 
492 		/* Last QH is the Terminating QH, it's different */
493 		if (i == SKEL_TERM) {
494 			if (qh_element(qh) != LINK_TO_TD(uhci, uhci->term_td)) {
495 				out += sprintf(out,
496 					"    skel_term_qh element is not set to term_td!\n");
497 				if (out - buf > len)
498 					goto done;
499 			}
500 			link = fsbr_link;
501 			if (!link)
502 				link = LINK_TO_QH(uhci, uhci->skel_term_qh);
503 			goto check_qh_link;
504 		}
505 
506 		head = &qh->node;
507 		tmp = head->next;
508 
509 		while (tmp != head) {
510 			qh = list_entry(tmp, struct uhci_qh, node);
511 			tmp = tmp->next;
512 			if (++cnt <= 10) {
513 				out += uhci_show_qh(uhci, qh, out,
514 						len - (out - buf), 4);
515 				if (out - buf > len)
516 					goto tail;
517 			}
518 			if (!fsbr_link && qh->skel >= SKEL_FSBR)
519 				fsbr_link = LINK_TO_QH(uhci, qh);
520 		}
521 		if ((cnt -= 10) > 0)
522 			out += sprintf(out, "    Skipped %d QHs\n", cnt);
523 
524 		link = UHCI_PTR_TERM(uhci);
525 		if (i <= SKEL_ISO)
526 			;
527 		else if (i < SKEL_ASYNC)
528 			link = LINK_TO_QH(uhci, uhci->skel_async_qh);
529 		else if (!uhci->fsbr_is_on)
530 			;
531 		else
532 			link = LINK_TO_QH(uhci, uhci->skel_term_qh);
533 check_qh_link:
534 		if (qh->link != link)
535 			out += sprintf(out,
536 				"    last QH not linked to next skeleton!\n");
537 
538 		if (out - buf > len)
539 			goto done;
540 	}
541 
542 done:
543 	if (out - buf > len)
544 		out += sprintf(out, " ...\n");
545 tail:
546 	return out - buf;
547 }
548 
549 #ifdef CONFIG_DEBUG_FS
550 
551 #define MAX_OUTPUT	(64 * 1024)
552 
553 struct uhci_debug {
554 	int size;
555 	char *data;
556 };
557 
558 static int uhci_debug_open(struct inode *inode, struct file *file)
559 {
560 	struct uhci_hcd *uhci = inode->i_private;
561 	struct uhci_debug *up;
562 	unsigned long flags;
563 
564 	up = kmalloc(sizeof(*up), GFP_KERNEL);
565 	if (!up)
566 		return -ENOMEM;
567 
568 	up->data = kmalloc(MAX_OUTPUT, GFP_KERNEL);
569 	if (!up->data) {
570 		kfree(up);
571 		return -ENOMEM;
572 	}
573 
574 	up->size = 0;
575 	spin_lock_irqsave(&uhci->lock, flags);
576 	if (uhci->is_initialized)
577 		up->size = uhci_sprint_schedule(uhci, up->data,
578 					MAX_OUTPUT - EXTRA_SPACE);
579 	spin_unlock_irqrestore(&uhci->lock, flags);
580 
581 	file->private_data = up;
582 
583 	return 0;
584 }
585 
586 static loff_t uhci_debug_lseek(struct file *file, loff_t off, int whence)
587 {
588 	struct uhci_debug *up = file->private_data;
589 	return no_seek_end_llseek_size(file, off, whence, up->size);
590 }
591 
592 static ssize_t uhci_debug_read(struct file *file, char __user *buf,
593 				size_t nbytes, loff_t *ppos)
594 {
595 	struct uhci_debug *up = file->private_data;
596 	return simple_read_from_buffer(buf, nbytes, ppos, up->data, up->size);
597 }
598 
599 static int uhci_debug_release(struct inode *inode, struct file *file)
600 {
601 	struct uhci_debug *up = file->private_data;
602 
603 	kfree(up->data);
604 	kfree(up);
605 
606 	return 0;
607 }
608 
609 static const struct file_operations uhci_debug_operations = {
610 	.owner =	THIS_MODULE,
611 	.open =		uhci_debug_open,
612 	.llseek =	uhci_debug_lseek,
613 	.read =		uhci_debug_read,
614 	.release =	uhci_debug_release,
615 };
616 #define UHCI_DEBUG_OPS
617 
618 #endif	/* CONFIG_DEBUG_FS */
619 
620 #else	/* CONFIG_DYNAMIC_DEBUG*/
621 
622 static inline void lprintk(char *buf)
623 {}
624 
625 static inline int uhci_show_qh(struct uhci_hcd *uhci,
626 		struct uhci_qh *qh, char *buf, int len, int space)
627 {
628 	return 0;
629 }
630 
631 static inline int uhci_sprint_schedule(struct uhci_hcd *uhci,
632 		char *buf, int len)
633 {
634 	return 0;
635 }
636 
637 #endif
638