1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /*
27  * Copyright (c) 2009-2010, Intel Corporation.
28  * All rights reserved.
29  */
30 /*
31  * Copyright 2012 Garrett D'Amore <garrett@damore.org>.  All rights reserved.
32  */
33 /*
34  * This module implements a nexus driver for the ACPI virtual bus.
35  * It does not handle any of the DDI functions passed up to it by the child
36  * drivers, but instead allows them to bubble up to the root node.
37  */
38 
39 #include <sys/types.h>
40 #include <sys/cmn_err.h>
41 #include <sys/conf.h>
42 #include <sys/modctl.h>
43 #include <sys/ddi.h>
44 #include <sys/ddi_impldefs.h>
45 #include <sys/ddifm.h>
46 #include <sys/note.h>
47 #include <sys/ndifm.h>
48 #include <sys/sunddi.h>
49 #include <sys/sunndi.h>
50 #include <sys/acpidev.h>
51 #include <sys/acpinex.h>
52 
53 /* Patchable through /etc/system. */
54 #ifdef	DEBUG
55 int acpinex_debug = 1;
56 #else
57 int acpinex_debug = 0;
58 #endif
59 
60 /*
61  * Driver globals
62  */
63 static kmutex_t acpinex_lock;
64 static void *acpinex_softstates;
65 
66 static int acpinex_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
67 static int acpinex_attach(dev_info_t *, ddi_attach_cmd_t);
68 static int acpinex_detach(dev_info_t *, ddi_detach_cmd_t);
69 static int acpinex_open(dev_t *, int, int, cred_t *);
70 static int acpinex_close(dev_t, int, int, cred_t *);
71 static int acpinex_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
72 static int acpinex_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
73     off_t offset, off_t len, caddr_t *vaddrp);
74 static int acpinex_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *,
75     void *);
76 static int acpinex_fm_init_child(dev_info_t *, dev_info_t *, int,
77     ddi_iblock_cookie_t *);
78 static void acpinex_fm_init(acpinex_softstate_t *softsp);
79 static void acpinex_fm_fini(acpinex_softstate_t *softsp);
80 
81 extern void make_ddi_ppd(dev_info_t *, struct ddi_parent_private_data **);
82 
83 /*
84  * Configuration data structures
85  */
86 static struct bus_ops acpinex_bus_ops = {
87 	BUSO_REV,			/* busops_rev */
88 	acpinex_bus_map,		/* bus_map */
89 	NULL,				/* bus_get_intrspec */
90 	NULL,				/* bus_add_intrspec */
91 	NULL,				/* bus_remove_intrspec */
92 	i_ddi_map_fault,		/* bus_map_fault */
93 	NULL,				/* bus_dma_map */
94 	ddi_dma_allochdl,		/* bus_dma_allochdl */
95 	ddi_dma_freehdl,		/* bus_dma_freehdl */
96 	ddi_dma_bindhdl,		/* bus_dma_bindhdl */
97 	ddi_dma_unbindhdl,		/* bus_dma_unbindhdl */
98 	ddi_dma_flush,			/* bus_dma_flush */
99 	ddi_dma_win,			/* bus_dma_win */
100 	ddi_dma_mctl,			/* bus_dma_ctl */
101 	acpinex_ctlops,			/* bus_ctl */
102 	ddi_bus_prop_op,		/* bus_prop_op */
103 	ndi_busop_get_eventcookie,	/* bus_get_eventcookie */
104 	ndi_busop_add_eventcall,	/* bus_add_eventcall */
105 	ndi_busop_remove_eventcall,	/* bus_remove_eventcall */
106 	ndi_post_event,			/* bus_post_event */
107 	NULL,				/* bus_intr_ctl */
108 	NULL,				/* bus_config */
109 	NULL,				/* bus_unconfig */
110 	acpinex_fm_init_child,		/* bus_fm_init */
111 	NULL,				/* bus_fm_fini */
112 	NULL,				/* bus_fm_access_enter */
113 	NULL,				/* bus_fm_access_exit */
114 	NULL,				/* bus_power */
115 	i_ddi_intr_ops			/* bus_intr_op */
116 };
117 
118 static struct cb_ops acpinex_cb_ops = {
119 	acpinex_open,			/* cb_open */
120 	acpinex_close,			/* cb_close */
121 	nodev,				/* cb_strategy */
122 	nodev,				/* cb_print */
123 	nodev,				/* cb_dump */
124 	nodev,				/* cb_read */
125 	nodev,				/* cb_write */
126 	acpinex_ioctl,			/* cb_ioctl */
127 	nodev,				/* cb_devmap */
128 	nodev,				/* cb_mmap */
129 	nodev,				/* cb_segmap */
130 	nochpoll,			/* cb_poll */
131 	ddi_prop_op,			/* cb_prop_op */
132 	NULL,				/* cb_str */
133 	D_NEW | D_MP | D_HOTPLUG,	/* Driver compatibility flag */
134 	CB_REV,				/* rev */
135 	nodev,				/* int (*cb_aread)() */
136 	nodev				/* int (*cb_awrite)() */
137 };
138 
139 static struct dev_ops acpinex_ops = {
140 	DEVO_REV,			/* devo_rev, */
141 	0,				/* devo_refcnt */
142 	acpinex_info,			/* devo_getinfo */
143 	nulldev,			/* devo_identify */
144 	nulldev,			/* devo_probe */
145 	acpinex_attach,			/* devo_attach */
146 	acpinex_detach,			/* devo_detach */
147 	nulldev,			/* devo_reset */
148 	&acpinex_cb_ops,		/* devo_cb_ops */
149 	&acpinex_bus_ops,		/* devo_bus_ops */
150 	nulldev,			/* devo_power */
151 	ddi_quiesce_not_needed		/* devo_quiesce */
152 };
153 
154 static struct modldrv modldrv = {
155 	&mod_driverops,			/* Type of module */
156 	"ACPI virtual bus driver",	/* name of module */
157 	&acpinex_ops,			/* driver ops */
158 };
159 
160 static struct modlinkage modlinkage = {
161 	MODREV_1,			/* rev */
162 	(void *)&modldrv,
163 	NULL
164 };
165 
166 /*
167  * Module initialization routines.
168  */
169 int
_init(void)170 _init(void)
171 {
172 	int error;
173 
174 	/* Initialize soft state pointer. */
175 	if ((error = ddi_soft_state_init(&acpinex_softstates,
176 	    sizeof (acpinex_softstate_t), 8)) != 0) {
177 		cmn_err(CE_WARN,
178 		    "acpinex: failed to initialize soft state structure.");
179 		return (error);
180 	}
181 
182 	/* Initialize event subsystem. */
183 	acpinex_event_init();
184 
185 	/* Install the module. */
186 	if ((error = mod_install(&modlinkage)) != 0) {
187 		cmn_err(CE_WARN, "acpinex: failed to install module.");
188 		ddi_soft_state_fini(&acpinex_softstates);
189 		return (error);
190 	}
191 
192 	mutex_init(&acpinex_lock, NULL, MUTEX_DRIVER, NULL);
193 
194 	return (0);
195 }
196 
197 int
_fini(void)198 _fini(void)
199 {
200 	int error;
201 
202 	/* Remove the module. */
203 	if ((error = mod_remove(&modlinkage)) != 0) {
204 		return (error);
205 	}
206 
207 	/* Shut down event subsystem. */
208 	acpinex_event_fini();
209 
210 	/* Free the soft state info. */
211 	ddi_soft_state_fini(&acpinex_softstates);
212 
213 	mutex_destroy(&acpinex_lock);
214 
215 	return (0);
216 }
217 
218 int
_info(struct modinfo * modinfop)219 _info(struct modinfo *modinfop)
220 {
221 	return (mod_info(&modlinkage, modinfop));
222 }
223 
224 static int
acpinex_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)225 acpinex_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
226 {
227 	_NOTE(ARGUNUSED(dip));
228 
229 	dev_t	dev;
230 	int	instance;
231 
232 	if (infocmd == DDI_INFO_DEVT2INSTANCE) {
233 		dev = (dev_t)arg;
234 		instance = ACPINEX_GET_INSTANCE(getminor(dev));
235 		*result = (void *)(uintptr_t)instance;
236 		return (DDI_SUCCESS);
237 	}
238 
239 	return (DDI_FAILURE);
240 }
241 
242 static int
acpinex_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)243 acpinex_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
244 {
245 	int instance;
246 	acpinex_softstate_t *softsp;
247 
248 	switch (cmd) {
249 	case DDI_ATTACH:
250 		break;
251 
252 	case DDI_RESUME:
253 		return (DDI_SUCCESS);
254 
255 	default:
256 		return (DDI_FAILURE);
257 	}
258 
259 	/* Get and check instance number. */
260 	instance = ddi_get_instance(devi);
261 	if (instance >= ACPINEX_INSTANCE_MAX) {
262 		cmn_err(CE_WARN, "acpinex: instance number %d is out of range "
263 		    "in acpinex_attach(), max %d.",
264 		    instance, ACPINEX_INSTANCE_MAX - 1);
265 		return (DDI_FAILURE);
266 	}
267 
268 	/* Get soft state structure. */
269 	if (ddi_soft_state_zalloc(acpinex_softstates, instance)
270 	    != DDI_SUCCESS) {
271 		cmn_err(CE_WARN, "!acpinex: failed to allocate soft state "
272 		    "object in acpinex_attach().");
273 		return (DDI_FAILURE);
274 	}
275 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
276 
277 	/* Initialize soft state structure */
278 	softsp->ans_dip = devi;
279 	(void) ddi_pathname(devi, softsp->ans_path);
280 	if (ACPI_FAILURE(acpica_get_handle(devi, &softsp->ans_hdl))) {
281 		ACPINEX_DEBUG(CE_WARN,
282 		    "!acpinex: failed to get ACPI handle for %s.",
283 		    softsp->ans_path);
284 		ddi_soft_state_free(acpinex_softstates, instance);
285 		return (DDI_FAILURE);
286 	}
287 	mutex_init(&softsp->ans_lock, NULL, MUTEX_DRIVER, NULL);
288 
289 	/* Install event handler for child/descendant objects. */
290 	if (acpinex_event_scan(softsp, B_TRUE) != DDI_SUCCESS) {
291 		cmn_err(CE_WARN, "!acpinex: failed to install event handler "
292 		    "for children of %s.", softsp->ans_path);
293 	}
294 
295 	/* nothing to suspend/resume here */
296 	(void) ddi_prop_update_string(DDI_DEV_T_NONE, devi,
297 	    "pm-hardware-state", "no-suspend-resume");
298 	(void) ddi_prop_update_int(DDI_DEV_T_NONE, devi,
299 	    DDI_NO_AUTODETACH, 1);
300 
301 	acpinex_fm_init(softsp);
302 	ddi_report_dev(devi);
303 
304 	return (DDI_SUCCESS);
305 }
306 
307 static int
acpinex_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)308 acpinex_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
309 {
310 	int instance;
311 	acpinex_softstate_t *softsp;
312 
313 	instance = ddi_get_instance(devi);
314 	if (instance >= ACPINEX_INSTANCE_MAX) {
315 		cmn_err(CE_WARN, "acpinex: instance number %d is out of range "
316 		    "in acpinex_detach(), max %d.",
317 		    instance, ACPINEX_INSTANCE_MAX - 1);
318 		return (DDI_FAILURE);
319 	}
320 
321 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
322 	if (softsp == NULL) {
323 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
324 		    "object for instance %d in acpinex_detach()", instance);
325 		return (DDI_FAILURE);
326 	}
327 
328 	switch (cmd) {
329 	case DDI_DETACH:
330 		if (acpinex_event_scan(softsp, B_FALSE) != DDI_SUCCESS) {
331 			cmn_err(CE_WARN, "!acpinex: failed to uninstall event "
332 			    "handler for children of %s.", softsp->ans_path);
333 			return (DDI_FAILURE);
334 		}
335 		ddi_remove_minor_node(devi, NULL);
336 		acpinex_fm_fini(softsp);
337 		mutex_destroy(&softsp->ans_lock);
338 		ddi_soft_state_free(acpinex_softstates, instance);
339 		(void) ddi_prop_update_int(DDI_DEV_T_NONE, devi,
340 		    DDI_NO_AUTODETACH, 0);
341 		return (DDI_SUCCESS);
342 
343 	case DDI_SUSPEND:
344 		return (DDI_SUCCESS);
345 
346 	default:
347 		return (DDI_FAILURE);
348 	}
349 }
350 
351 static int
name_child(dev_info_t * child,char * name,int namelen)352 name_child(dev_info_t *child, char *name, int namelen)
353 {
354 	char *unitaddr;
355 
356 	ddi_set_parent_data(child, NULL);
357 
358 	name[0] = '\0';
359 	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
360 	    ACPIDEV_PROP_NAME_UNIT_ADDR, &unitaddr) == DDI_SUCCESS) {
361 		(void) strlcpy(name, unitaddr, namelen);
362 		ddi_prop_free(unitaddr);
363 	} else {
364 		ACPINEX_DEBUG(CE_NOTE, "!acpinex: failed to lookup child "
365 		    "unit-address prop for %p.", (void *)child);
366 	}
367 
368 	return (DDI_SUCCESS);
369 }
370 
371 static int
init_child(dev_info_t * child)372 init_child(dev_info_t *child)
373 {
374 	char name[MAXNAMELEN];
375 
376 	(void) name_child(child, name, MAXNAMELEN);
377 	ddi_set_name_addr(child, name);
378 	if ((ndi_dev_is_persistent_node(child) == 0) &&
379 	    (ndi_merge_node(child, name_child) == DDI_SUCCESS)) {
380 		impl_ddi_sunbus_removechild(child);
381 		return (DDI_FAILURE);
382 	}
383 
384 	return (DDI_SUCCESS);
385 }
386 
387 /*
388  * Control ops entry point:
389  *
390  * Requests handled completely:
391  *      DDI_CTLOPS_INITCHILD
392  *      DDI_CTLOPS_UNINITCHILD
393  * All others are passed to the parent.
394  */
395 static int
acpinex_ctlops(dev_info_t * dip,dev_info_t * rdip,ddi_ctl_enum_t op,void * arg,void * result)396 acpinex_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg,
397     void *result)
398 {
399 	int rval = DDI_SUCCESS;
400 
401 	switch (op) {
402 	case DDI_CTLOPS_INITCHILD:
403 		rval = init_child((dev_info_t *)arg);
404 		break;
405 
406 	case DDI_CTLOPS_UNINITCHILD:
407 		impl_ddi_sunbus_removechild((dev_info_t *)arg);
408 		break;
409 
410 	case DDI_CTLOPS_REPORTDEV: {
411 		if (rdip == (dev_info_t *)0)
412 			return (DDI_FAILURE);
413 		cmn_err(CE_CONT, "?acpinex: %s@%s, %s%d\n",
414 		    ddi_node_name(rdip), ddi_get_name_addr(rdip),
415 		    ddi_driver_name(rdip), ddi_get_instance(rdip));
416 		break;
417 	}
418 
419 	default:
420 		rval = ddi_ctlops(dip, rdip, op, arg, result);
421 		break;
422 	}
423 
424 	return (rval);
425 }
426 
427 /* ARGSUSED */
428 static int
acpinex_bus_map(dev_info_t * dip,dev_info_t * rdip,ddi_map_req_t * mp,off_t offset,off_t len,caddr_t * vaddrp)429 acpinex_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
430     off_t offset, off_t len, caddr_t *vaddrp)
431 {
432 	ACPINEX_DEBUG(CE_WARN,
433 	    "!acpinex: acpinex_bus_map called and it's unimplemented.");
434 	return (DDI_ME_UNIMPLEMENTED);
435 }
436 
437 static int
acpinex_open(dev_t * devi,int flags,int otyp,cred_t * credp)438 acpinex_open(dev_t *devi, int flags, int otyp, cred_t *credp)
439 {
440 	_NOTE(ARGUNUSED(flags, otyp, credp));
441 
442 	minor_t minor, instance;
443 	acpinex_softstate_t *softsp;
444 
445 	minor = getminor(*devi);
446 	instance = ACPINEX_GET_INSTANCE(minor);
447 	if (instance >= ACPINEX_INSTANCE_MAX) {
448 		ACPINEX_DEBUG(CE_WARN, "!acpinex: instance number %d out of "
449 		    "range in acpinex_open, max %d.",
450 		    instance, ACPINEX_INSTANCE_MAX - 1);
451 		return (EINVAL);
452 	}
453 
454 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
455 	if (softsp == NULL) {
456 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
457 		    "object for instance %d in acpinex_open().", instance);
458 		return (EINVAL);
459 	}
460 
461 	if (ACPINEX_IS_DEVCTL(minor)) {
462 		return (0);
463 	} else {
464 		ACPINEX_DEBUG(CE_WARN,
465 		    "!acpinex: invalid minor number %d in acpinex_open().",
466 		    minor);
467 		return (EINVAL);
468 	}
469 }
470 
471 static int
acpinex_close(dev_t dev,int flags,int otyp,cred_t * credp)472 acpinex_close(dev_t dev, int flags, int otyp, cred_t *credp)
473 {
474 	_NOTE(ARGUNUSED(flags, otyp, credp));
475 
476 	minor_t minor, instance;
477 	acpinex_softstate_t *softsp;
478 
479 	minor = getminor(dev);
480 	instance = ACPINEX_GET_INSTANCE(minor);
481 	if (instance >= ACPINEX_INSTANCE_MAX) {
482 		ACPINEX_DEBUG(CE_WARN, "!acpinex: instance number %d out of "
483 		    "range in acpinex_close(), max %d.",
484 		    instance, ACPINEX_INSTANCE_MAX - 1);
485 		return (EINVAL);
486 	}
487 
488 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
489 	if (softsp == NULL) {
490 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
491 		    "object for instance %d in acpinex_close().", instance);
492 		return (EINVAL);
493 	}
494 
495 	if (ACPINEX_IS_DEVCTL(minor)) {
496 		return (0);
497 	} else {
498 		ACPINEX_DEBUG(CE_WARN,
499 		    "!acpinex: invalid minor number %d in acpinex_close().",
500 		    minor);
501 		return (EINVAL);
502 	}
503 }
504 
505 static int
acpinex_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)506 acpinex_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
507     int *rvalp)
508 {
509 	_NOTE(ARGUNUSED(cmd, arg, mode, credp, rvalp));
510 
511 	int rv = 0;
512 	minor_t minor, instance;
513 	acpinex_softstate_t *softsp;
514 
515 	minor = getminor(dev);
516 	instance = ACPINEX_GET_INSTANCE(minor);
517 	if (instance >= ACPINEX_INSTANCE_MAX) {
518 		ACPINEX_DEBUG(CE_NOTE, "!acpinex: instance number %d out of "
519 		    "range in acpinex_ioctl(), max %d.",
520 		    instance, ACPINEX_INSTANCE_MAX - 1);
521 		return (EINVAL);
522 	}
523 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
524 	if (softsp == NULL) {
525 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
526 		    "object for instance %d in acpinex_ioctl().", instance);
527 		return (EINVAL);
528 	}
529 
530 	rv = ENOTSUP;
531 	ACPINEX_DEBUG(CE_WARN,
532 	    "!acpinex: invalid minor number %d in acpinex_ioctl().", minor);
533 
534 	return (rv);
535 }
536 
537 /*
538  * FMA error callback.
539  * Register error handling callback with our parent. We will just call
540  * our children's error callbacks and return their status.
541  */
542 static int
acpinex_err_callback(dev_info_t * dip,ddi_fm_error_t * derr,const void * impl_data)543 acpinex_err_callback(dev_info_t *dip, ddi_fm_error_t *derr,
544     const void *impl_data)
545 {
546 	_NOTE(ARGUNUSED(impl_data));
547 
548 	/* Call our childrens error handlers */
549 	return (ndi_fm_handler_dispatch(dip, NULL, derr));
550 }
551 
552 /*
553  * Initialize our FMA resources
554  */
555 static void
acpinex_fm_init(acpinex_softstate_t * softsp)556 acpinex_fm_init(acpinex_softstate_t *softsp)
557 {
558 	softsp->ans_fm_cap = DDI_FM_EREPORT_CAPABLE | DDI_FM_ERRCB_CAPABLE |
559 	    DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE;
560 
561 	/*
562 	 * Request our capability level and get our parent's capability and ibc.
563 	 */
564 	ddi_fm_init(softsp->ans_dip, &softsp->ans_fm_cap, &softsp->ans_fm_ibc);
565 	if (softsp->ans_fm_cap & DDI_FM_ERRCB_CAPABLE) {
566 		/*
567 		 * Register error callback with our parent if supported.
568 		 */
569 		ddi_fm_handler_register(softsp->ans_dip, acpinex_err_callback,
570 		    softsp);
571 	}
572 }
573 
574 /*
575  * Breakdown our FMA resources
576  */
577 static void
acpinex_fm_fini(acpinex_softstate_t * softsp)578 acpinex_fm_fini(acpinex_softstate_t *softsp)
579 {
580 	/* Clean up allocated fm structures */
581 	if (softsp->ans_fm_cap & DDI_FM_ERRCB_CAPABLE) {
582 		ddi_fm_handler_unregister(softsp->ans_dip);
583 	}
584 	ddi_fm_fini(softsp->ans_dip);
585 }
586 
587 /*
588  * Initialize FMA resources for child devices.
589  * Called when child calls ddi_fm_init().
590  */
591 static int
acpinex_fm_init_child(dev_info_t * dip,dev_info_t * tdip,int cap,ddi_iblock_cookie_t * ibc)592 acpinex_fm_init_child(dev_info_t *dip, dev_info_t *tdip, int cap,
593     ddi_iblock_cookie_t *ibc)
594 {
595 	_NOTE(ARGUNUSED(tdip, cap));
596 
597 	acpinex_softstate_t *softsp = ddi_get_soft_state(acpinex_softstates,
598 	    ddi_get_instance(dip));
599 
600 	*ibc = softsp->ans_fm_ibc;
601 
602 	return (softsp->ans_fm_cap);
603 }
604