xref: /openbsd-src/usr.sbin/acpidump/acpidump.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: acpidump.c,v 1.10 2014/07/08 10:28:02 deraadt Exp $	*/
2 /*
3  * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  */
28 
29 #include <sys/types.h>
30 #include <sys/mman.h>
31 #include <sys/queue.h>
32 #include <sys/stat.h>
33 
34 #include <assert.h>
35 #include <err.h>
36 #include <fcntl.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <limits.h>
42 
43 
44 #define vm_page_size sysconf(_SC_PAGESIZE)
45 #define PRINTFLAG(xx)							\
46 	do {								\
47 		if (facp->flags & ACPI_FACP_FLAG_## xx) {		\
48 			fprintf(fhdr, "%c%s", sep, #xx); sep = ',';	\
49 		}							\
50 	} while (0)
51 
52 
53 typedef unsigned long	vm_offset_t;
54 
55 struct ACPIrsdp {
56 	u_char		signature[8];
57 	u_char		sum;
58 	u_char		oem[6];
59 	u_char		res;
60 	u_int32_t	addr;
61 } __packed;
62 
63 struct ACPIsdt {
64 	u_char		signature[4];
65 	u_int32_t	len;
66 	u_char		rev;
67 	u_char		check;
68 	u_char		oemid[6];
69 	u_char		oemtblid[8];
70 	u_int32_t	oemrev;
71 	u_char		creator[4];
72 	u_int32_t	crerev;
73 #define SIZEOF_SDT_HDR	36	/* struct size except body */
74 	u_int32_t	body[1];/* This member should be casted */
75 } __packed;
76 
77 struct ACPIgas {
78 	u_int8_t	address_space_id;
79 #define ACPI_GAS_MEMORY		0
80 #define ACPI_GAS_IO		1
81 #define ACPI_GAS_PCI		2
82 #define ACPI_GAS_EMBEDDED	3
83 #define ACPI_GAS_SMBUS		4
84 #define ACPI_GAS_FIXED		0x7f
85 	u_int8_t	register_bit_width;
86 	u_int8_t	register_bit_offset;
87 	u_int8_t	res;
88 	u_int64_t	address;
89 } __packed;
90 
91 struct FACPbody {
92 	u_int32_t	facs_ptr;
93 	u_int32_t	dsdt_ptr;
94 	u_int8_t	int_model;
95 #define ACPI_FACP_INTMODEL_PIC	0	/* Standard PC-AT PIC */
96 #define ACPI_FACP_INTMODEL_APIC	1	/* Multiple APIC */
97 	u_char		reserved1;
98 	u_int16_t	sci_int;
99 	u_int32_t	smi_cmd;
100 	u_int8_t	acpi_enable;
101 	u_int8_t	acpi_disable;
102 	u_int8_t	s4biosreq;
103 	u_int8_t	reserved2;
104 	u_int32_t	pm1a_evt_blk;
105 	u_int32_t	pm1b_evt_blk;
106 	u_int32_t	pm1a_cnt_blk;
107 	u_int32_t	pm1b_cnt_blk;
108 	u_int32_t	pm2_cnt_blk;
109 	u_int32_t	pm_tmr_blk;
110 	u_int32_t	gpe0_blk;
111 	u_int32_t	gpe1_blk;
112 	u_int8_t	pm1_evt_len;
113 	u_int8_t	pm1_cnt_len;
114 	u_int8_t	pm2_cnt_len;
115 	u_int8_t	pm_tmr_len;
116 	u_int8_t	gpe0_len;
117 	u_int8_t	gpe1_len;
118 	u_int8_t	gpe1_base;
119 	u_int8_t	reserved3;
120 	u_int16_t	p_lvl2_lat;
121 	u_int16_t	p_lvl3_lat;
122 	u_int16_t	flush_size;
123 	u_int16_t	flush_stride;
124 	u_int8_t	duty_off;
125 	u_int8_t	duty_width;
126 	u_int8_t	day_alrm;
127 	u_int8_t	mon_alrm;
128 	u_int8_t	century;
129 	u_int16_t	iapc_boot_arch;
130 	u_char		reserved4[1];
131 	u_int32_t	flags;
132 #define ACPI_FACP_FLAG_WBINVD	1	/* WBINVD is correctly supported */
133 #define ACPI_FACP_FLAG_WBINVD_FLUSH 2	/* WBINVD flushes caches */
134 #define ACPI_FACP_FLAG_PROC_C1	4	/* C1 power state supported */
135 #define ACPI_FACP_FLAG_P_LVL2_UP 8	/* C2 power state works on SMP */
136 #define ACPI_FACP_FLAG_PWR_BUTTON 16	/* Power button uses control method */
137 #define ACPI_FACP_FLAG_SLP_BUTTON 32	/* Sleep button uses control method */
138 #define ACPI_FACP_FLAG_FIX_RTC	64	/* RTC wakeup not supported */
139 #define ACPI_FACP_FLAG_RTC_S4	128	/* RTC can wakeup from S4 state */
140 #define ACPI_FACP_FLAG_TMR_VAL_EXT 256	/* TMR_VAL is 32bit */
141 #define ACPI_FACP_FLAG_DCK_CAP	512	/* Can support docking */
142 	struct ACPIgas	reset_reg;
143 	u_int8_t	reset_value;
144 	u_int8_t	reserved5[3];
145 	u_int64_t	x_firmware_ctrl;
146 	u_int64_t	x_dsdt;
147 	struct ACPIgas	x_pm1a_evt_blk;
148 	struct ACPIgas	x_pm1b_evt_blk;
149 	struct ACPIgas	x_pm1a_cnt_blk;
150 	struct ACPIgas	x_pm1b_cnt_blk;
151 	struct ACPIgas	x_pm2_cnt_blk;
152 	struct ACPIgas	x_pm_tmr_blk;
153 	struct ACPIgas	x_gpe0_blk;
154 	struct ACPIgas	x_gpe1_blk;
155 } __packed;
156 
157 struct acpi_user_mapping {
158 	LIST_ENTRY(acpi_user_mapping)	link;
159 	vm_offset_t			pa;
160 	caddr_t				va;
161 	size_t				size;
162 };
163 
164 LIST_HEAD(acpi_user_mapping_list, acpi_user_mapping) maplist;
165 
166 int		acpi_mem_fd = -1;
167 char		*aml_dumpfile;
168 FILE		*fhdr;
169 
170 int	acpi_checksum(void *_p, size_t _length);
171 struct acpi_user_mapping *acpi_user_find_mapping(vm_offset_t _pa, size_t _size);
172 void	*acpi_map_physical(vm_offset_t _pa, size_t _size);
173 void	acpi_user_init(void);
174 struct ACPIrsdp *acpi_find_rsd_ptr(void);
175 void	acpi_print_string(char *_s, size_t _length);
176 void	acpi_print_rsd_ptr(struct ACPIrsdp *_rp);
177 struct ACPIsdt *acpi_map_sdt(vm_offset_t _pa);
178 void	aml_dump(struct ACPIsdt *_hdr);
179 void	acpi_print_sdt(struct ACPIsdt *_sdp);
180 void	acpi_print_rsdt(struct ACPIsdt *_rsdp);
181 void	acpi_print_facp(struct FACPbody *_facp);
182 void	acpi_print_dsdt(struct ACPIsdt *_dsdp);
183 void	acpi_handle_dsdt(struct ACPIsdt *_dsdp);
184 void	acpi_handle_facp(struct FACPbody *_facp);
185 void	acpi_handle_rsdt(struct ACPIsdt *_rsdp);
186 void	asl_dump_from_devmem(void);
187 void	usage(void);
188 
189 
190 struct ACPIsdt	dsdt_header = {
191 	"DSDT", 0, 1, 0, "OEMID", "OEMTBLID", 0x12345678, "CRTR", 0x12345678
192 };
193 
194 int
195 acpi_checksum(void *p, size_t length)
196 {
197 	u_int8_t	*bp;
198 	u_int8_t	sum;
199 
200 	bp = p;
201 	sum = 0;
202 	while (length--)
203 		sum += *bp++;
204 
205 	return (sum);
206 }
207 
208 struct acpi_user_mapping *
209 acpi_user_find_mapping(vm_offset_t pa, size_t size)
210 {
211 	struct acpi_user_mapping	*map;
212 	int	page_mask = getpagesize() - 1;
213 
214 	/* First search for an existing mapping */
215 	for (map = LIST_FIRST(&maplist); map; map = LIST_NEXT(map, link)) {
216 		if (map->pa <= pa && map->size >= pa + size - map->pa)
217 			return (map);
218 	}
219 
220 	/* Then create a new one */
221 #undef round_page
222 #undef trunc_page
223 #define	round_page(x)	(((x) + page_mask) & ~page_mask)
224 #define	trunc_page(x)	((x) & ~page_mask)
225 	size = round_page(pa + size) - trunc_page(pa);
226 	pa = trunc_page(pa);
227 #undef round_page
228 #undef trunc_page
229 	map = malloc(sizeof(struct acpi_user_mapping));
230 	if (!map)
231 		errx(1, "out of memory");
232 	map->pa = pa;
233 	map->va = mmap(0, size, PROT_READ, MAP_SHARED, acpi_mem_fd, pa);
234 	map->size = size;
235 	if (map->va == MAP_FAILED)
236 		err(1, "can't map address");
237 	LIST_INSERT_HEAD(&maplist, map, link);
238 
239 	return (map);
240 }
241 
242 void *
243 acpi_map_physical(vm_offset_t pa, size_t size)
244 {
245 	struct acpi_user_mapping	*map;
246 
247 	map = acpi_user_find_mapping(pa, size);
248 	return (map->va + (pa - map->pa));
249 }
250 
251 void
252 acpi_user_init(void)
253 {
254 	if (acpi_mem_fd == -1) {
255 		acpi_mem_fd = open("/dev/mem", O_RDONLY);
256 		if (acpi_mem_fd == -1)
257 			err(1, "opening /dev/mem");
258 		LIST_INIT(&maplist);
259 	}
260 }
261 
262 struct ACPIrsdp *
263 acpi_find_rsd_ptr(void)
264 {
265 	int		i;
266 	u_int8_t	buf[sizeof(struct ACPIrsdp)];
267 
268 	acpi_user_init();
269 	for (i = 0; i < 1024 * 1024; i += 16) {
270 		lseek(acpi_mem_fd, i, SEEK_SET);
271 		read(acpi_mem_fd, buf, 16);
272 		if (!memcmp(buf, "RSD PTR ", 8)) {
273 			/* Read the rest of the structure */
274 			read(acpi_mem_fd, buf + 16, sizeof(struct ACPIrsdp) - 16);
275 
276 			/* Verify checksum before accepting it. */
277 			if (acpi_checksum(buf, sizeof(struct ACPIrsdp)))
278 				continue;
279 
280 			return (acpi_map_physical(i, sizeof(struct ACPIrsdp)));
281 		}
282 	}
283 
284 	return (0);
285 }
286 
287 void
288 acpi_print_string(char *s, size_t length)
289 {
290 	int		c;
291 
292 	/* Trim trailing spaces and NULLs */
293 	while (length > 0 && (s[length - 1] == ' ' || s[length - 1] == '\0'))
294 		length--;
295 
296 	while (length--) {
297 		c = *s++;
298 		fputc(c, fhdr);
299 	}
300 }
301 
302 void
303 acpi_print_rsd_ptr(struct ACPIrsdp *rp)
304 {
305 	fprintf(fhdr, "\n");
306 	fprintf(fhdr, "RSD PTR: Checksum=%d, OEMID=", rp->sum);
307 	acpi_print_string(rp->oem, 6);
308 	fprintf(fhdr, ", RsdtAddress=0x%08x\n", rp->addr);
309 	fprintf(fhdr, "\n");
310 }
311 
312 struct ACPIsdt *
313 acpi_map_sdt(vm_offset_t pa)
314 {
315 	struct ACPIsdt	*sp;
316 
317 	sp = acpi_map_physical(pa, sizeof(struct ACPIsdt));
318 	sp = acpi_map_physical(pa, sp->len);
319 	return (sp);
320 }
321 
322 void
323 aml_dump(struct ACPIsdt *hdr)
324 {
325 	static int	hdr_index;
326 	char		name[PATH_MAX];
327 	int		fd;
328 	mode_t		mode;
329 
330 	snprintf(name, sizeof(name), "%s.%c%c%c%c.%d",
331 	    aml_dumpfile, hdr->signature[0], hdr->signature[1],
332 	    hdr->signature[2], hdr->signature[3],
333 	    hdr_index++);
334 
335 	mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
336 	fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, mode);
337 	if (fd == -1)
338 		err(1, "aml_dump");
339 
340 	write(fd, hdr, SIZEOF_SDT_HDR);
341 	write(fd, hdr->body, hdr->len - SIZEOF_SDT_HDR);
342 	close(fd);
343 }
344 
345 void
346 acpi_print_sdt(struct ACPIsdt *sdp)
347 {
348 	fprintf(fhdr, "\n");
349 	acpi_print_string(sdp->signature, 4);
350 	fprintf(fhdr, ": Length=%d, Revision=%d, Checksum=%d,\n",
351 	       sdp->len, sdp->rev, sdp->check);
352 	fprintf(fhdr, "\tOEMID=");
353 	acpi_print_string(sdp->oemid, 6);
354 	fprintf(fhdr, ", OEM Table ID=");
355 	acpi_print_string(sdp->oemtblid, 8);
356 	fprintf(fhdr, ", OEM Revision=0x%x,\n", sdp->oemrev);
357 	fprintf(fhdr, "\tCreator ID=");
358 	acpi_print_string(sdp->creator, 4);
359 	fprintf(fhdr, ", Creator Revision=0x%x\n", sdp->crerev);
360 	fprintf(fhdr, "\n");
361 	if (!memcmp(sdp->signature, "DSDT", 4))
362 		memcpy(&dsdt_header, sdp, sizeof(dsdt_header));
363 }
364 
365 void
366 acpi_print_rsdt(struct ACPIsdt *rsdp)
367 {
368 	int		i, entries;
369 
370 	acpi_print_sdt(rsdp);
371 	entries = (rsdp->len - SIZEOF_SDT_HDR) / sizeof(u_int32_t);
372 	fprintf(fhdr, "\n");
373 	fprintf(fhdr, "\tEntries={ ");
374 	for (i = 0; i < entries; i++) {
375 		if (i > 0)
376 			fprintf(fhdr, ", ");
377 		fprintf(fhdr, "0x%08x", rsdp->body[i]);
378 	}
379 	fprintf(fhdr, " }\n");
380 	fprintf(fhdr, "\n");
381 }
382 
383 void
384 acpi_print_facp(struct FACPbody *facp)
385 {
386 	char		sep;
387 
388 	fprintf(fhdr, "\n");
389 	fprintf(fhdr, "\tDSDT=0x%x\n", facp->dsdt_ptr);
390 	fprintf(fhdr, "\tINT_MODEL=%s\n", facp->int_model ? "APIC" : "PIC");
391 	fprintf(fhdr, "\tSCI_INT=%d\n", facp->sci_int);
392 	fprintf(fhdr, "\tSMI_CMD=0x%x, ", facp->smi_cmd);
393 	fprintf(fhdr, "ACPI_ENABLE=0x%x, ", facp->acpi_enable);
394 	fprintf(fhdr, "ACPI_DISABLE=0x%x, ", facp->acpi_disable);
395 	fprintf(fhdr, "S4BIOS_REQ=0x%x\n", facp->s4biosreq);
396 	if (facp->pm1a_evt_blk)
397 		fprintf(fhdr, "\tPM1a_EVT_BLK=0x%x-0x%x\n",
398 		    facp->pm1a_evt_blk,
399 		    facp->pm1a_evt_blk + facp->pm1_evt_len - 1);
400 	if (facp->pm1b_evt_blk)
401 		fprintf(fhdr, "\tPM1b_EVT_BLK=0x%x-0x%x\n",
402 		    facp->pm1b_evt_blk,
403 		    facp->pm1b_evt_blk + facp->pm1_evt_len - 1);
404 	if (facp->pm1a_cnt_blk)
405 		fprintf(fhdr, "\tPM1a_CNT_BLK=0x%x-0x%x\n",
406 		    facp->pm1a_cnt_blk,
407 		    facp->pm1a_cnt_blk + facp->pm1_cnt_len - 1);
408 	if (facp->pm1b_cnt_blk)
409 		fprintf(fhdr, "\tPM1b_CNT_BLK=0x%x-0x%x\n",
410 		    facp->pm1b_cnt_blk,
411 		    facp->pm1b_cnt_blk + facp->pm1_cnt_len - 1);
412 	if (facp->pm2_cnt_blk)
413 		fprintf(fhdr, "\tPM2_CNT_BLK=0x%x-0x%x\n",
414 		    facp->pm2_cnt_blk,
415 		    facp->pm2_cnt_blk + facp->pm2_cnt_len - 1);
416 	if (facp->pm_tmr_blk)
417 		fprintf(fhdr, "\tPM2_TMR_BLK=0x%x-0x%x\n",
418 		    facp->pm_tmr_blk,
419 		    facp->pm_tmr_blk + facp->pm_tmr_len - 1);
420 	if (facp->gpe0_blk)
421 		fprintf(fhdr, "\tPM2_GPE0_BLK=0x%x-0x%x\n",
422 		    facp->gpe0_blk,
423 		    facp->gpe0_blk + facp->gpe0_len - 1);
424 	if (facp->gpe1_blk)
425 		fprintf(fhdr, "\tPM2_GPE1_BLK=0x%x-0x%x, GPE1_BASE=%d\n",
426 		    facp->gpe1_blk,
427 		    facp->gpe1_blk + facp->gpe1_len - 1,
428 		    facp->gpe1_base);
429 	fprintf(fhdr, "\tP_LVL2_LAT=%dms, P_LVL3_LAT=%dms\n",
430 	    facp->p_lvl2_lat, facp->p_lvl3_lat);
431 	fprintf(fhdr, "\tFLUSH_SIZE=%d, FLUSH_STRIDE=%d\n",
432 	    facp->flush_size, facp->flush_stride);
433 	fprintf(fhdr, "\tDUTY_OFFSET=%d, DUTY_WIDTH=%d\n",
434 	    facp->duty_off, facp->duty_width);
435 	fprintf(fhdr, "\tDAY_ALRM=%d, MON_ALRM=%d, CENTURY=%d\n",
436 	    facp->day_alrm, facp->mon_alrm, facp->century);
437 	fprintf(fhdr, "\tFlags=");
438 	sep = '{';
439 
440 	PRINTFLAG(WBINVD);
441 	PRINTFLAG(WBINVD_FLUSH);
442 	PRINTFLAG(PROC_C1);
443 	PRINTFLAG(P_LVL2_UP);
444 	PRINTFLAG(PWR_BUTTON);
445 	PRINTFLAG(SLP_BUTTON);
446 	PRINTFLAG(FIX_RTC);
447 	PRINTFLAG(RTC_S4);
448 	PRINTFLAG(TMR_VAL_EXT);
449 	PRINTFLAG(DCK_CAP);
450 
451 	fprintf(fhdr, "}\n");
452 	fprintf(fhdr, "\n");
453 }
454 
455 void
456 acpi_print_dsdt(struct ACPIsdt *dsdp)
457 {
458 	acpi_print_sdt(dsdp);
459 }
460 
461 void
462 acpi_handle_dsdt(struct ACPIsdt *dsdp)
463 {
464 	u_int8_t	*dp;
465 	u_int8_t	*end;
466 
467 	acpi_print_dsdt(dsdp);
468 
469 	dp = (u_int8_t *)dsdp->body;
470 	end = (u_int8_t *)dsdp + dsdp->len;
471 }
472 
473 void
474 acpi_handle_facp(struct FACPbody *facp)
475 {
476 	struct ACPIsdt	*dsdp;
477 
478 	acpi_print_facp(facp);
479 	dsdp = (struct ACPIsdt *) acpi_map_sdt(facp->dsdt_ptr);
480 	if (acpi_checksum(dsdp, dsdp->len))
481 		errx(1, "DSDT is corrupt");
482 	acpi_handle_dsdt(dsdp);
483 	aml_dump(dsdp);
484 }
485 
486 void
487 acpi_handle_rsdt(struct ACPIsdt *rsdp)
488 {
489 	int		i;
490 	int		entries;
491 	struct ACPIsdt	*sdp;
492 
493 	aml_dump(rsdp);
494 	entries = (rsdp->len - SIZEOF_SDT_HDR) / sizeof(u_int32_t);
495 	acpi_print_rsdt(rsdp);
496 	for (i = 0; i < entries; i++) {
497 		sdp = (struct ACPIsdt *) acpi_map_sdt(rsdp->body[i]);
498 		if (acpi_checksum(sdp, sdp->len))
499 			errx(1, "RSDT entry %d is corrupt", i);
500 		aml_dump(sdp);
501 		if (!memcmp(sdp->signature, "FACP", 4)) {
502 			acpi_handle_facp((struct FACPbody *) sdp->body);
503 		} else {
504 			acpi_print_sdt(sdp);
505 		}
506 	}
507 }
508 
509 void
510 asl_dump_from_devmem(void)
511 {
512 	struct ACPIrsdp	*rp;
513 	struct ACPIsdt	*rsdp;
514 	char		name[PATH_MAX];
515 
516 	snprintf(name, sizeof(name), "%s.headers", aml_dumpfile);
517 
518 	rp = acpi_find_rsd_ptr();
519 	if (!rp)
520 		errx(1, "Can't find ACPI information");
521 
522 	fhdr = fopen(name, "w");
523 	if (fhdr == NULL)
524 		err(1, "asl_dump_from_devmem");
525 
526 	acpi_print_rsd_ptr(rp);
527 	rsdp = (struct ACPIsdt *) acpi_map_sdt(rp->addr);
528 	if (memcmp(rsdp->signature, "RSDT", 4) ||
529 	    acpi_checksum(rsdp, rsdp->len))
530 		errx(1, "RSDT is corrupted");
531 
532 	acpi_handle_rsdt(rsdp);
533 
534 	fclose(fhdr);
535 }
536 
537 void
538 usage(void)
539 {
540 	extern char	*__progname;
541 
542 	fprintf(stderr, "%s -o prefix_for_output\n", __progname);
543 	exit(1);
544 }
545 
546 int
547 main(int argc, char *argv[])
548 {
549 	char		c;
550 
551 	while ((c = getopt(argc, argv, "o:")) != -1) {
552 		if (c == 'o')
553 			aml_dumpfile = optarg;
554 		else
555 			usage();
556 	}
557 
558 	if (aml_dumpfile == NULL)
559 		usage();
560 
561 	asl_dump_from_devmem();
562 
563 	return (0);
564 }
565