1 /* $Id$
2  *
3  * This file is subject to the terms and conditions of the GNU General Public
4  * License.  See the file "COPYING" in the main directory of this archive
5  * for more details.
6  *
7  * Copyright (C) 1992 - 1997, 2000-2003 Silicon Graphics, Inc. All rights reserved.
8  */
9 
10 
11 #include <linux/types.h>
12 #include <linux/ctype.h>
13 #include <asm/sn/sgi.h>
14 #include <asm/sn/sn_sal.h>
15 #include <asm/sn/io.h>
16 #include <asm/sn/sn_cpuid.h>
17 #include <asm/sn/iograph.h>
18 #include <asm/sn/invent.h>
19 #include <asm/sn/hcl.h>
20 #include <asm/sn/labelcl.h>
21 #include <asm/sn/klconfig.h>
22 #include <asm/sn/nodepda.h>
23 #include <asm/sn/module.h>
24 #include <asm/sn/router.h>
25 #include <asm/sn/xtalk/xbow.h>
26 
27 
28 #define LDEBUG 0
29 #define NIC_UNKNOWN ((nic_t) -1)
30 
31 #undef DEBUG_KLGRAPH
32 #ifdef DEBUG_KLGRAPH
33 #define DBG(x...) printk(x)
34 #else
35 #define DBG(x...)
36 #endif /* DEBUG_KLGRAPH */
37 
38 u64 klgraph_addr[MAX_COMPACT_NODES];
39 static int hasmetarouter;
40 
41 
42 char brick_types[MAX_BRICK_TYPES + 1] = "crikxdpn%#=vo2345";
43 
44 lboard_t *
find_lboard(lboard_t * start,unsigned char brd_type)45 find_lboard(lboard_t *start, unsigned char brd_type)
46 {
47 	/* Search all boards stored on this node. */
48 	while (start) {
49 		if (start->brd_type == brd_type)
50 			return start;
51 		start = KLCF_NEXT(start);
52 	}
53 
54 	/* Didn't find it. */
55 	return (lboard_t *)NULL;
56 }
57 
58 lboard_t *
find_lboard_class(lboard_t * start,unsigned char brd_type)59 find_lboard_class(lboard_t *start, unsigned char brd_type)
60 {
61 	/* Search all boards stored on this node. */
62 	while (start) {
63 		if (KLCLASS(start->brd_type) == KLCLASS(brd_type))
64 			return start;
65 		start = KLCF_NEXT(start);
66 	}
67 
68 	/* Didn't find it. */
69 	return (lboard_t *)NULL;
70 }
71 
72 klinfo_t *
find_component(lboard_t * brd,klinfo_t * kli,unsigned char struct_type)73 find_component(lboard_t *brd, klinfo_t *kli, unsigned char struct_type)
74 {
75 	int index, j;
76 
77 	if (kli == (klinfo_t *)NULL) {
78 		index = 0;
79 	} else {
80 		for (j = 0; j < KLCF_NUM_COMPS(brd); j++) {
81 			if (kli == KLCF_COMP(brd, j))
82 				break;
83 		}
84 		index = j;
85 		if (index == KLCF_NUM_COMPS(brd)) {
86 			DBG("find_component: Bad pointer: 0x%p\n", kli);
87 			return (klinfo_t *)NULL;
88 		}
89 		index++;	/* next component */
90 	}
91 
92 	for (; index < KLCF_NUM_COMPS(brd); index++) {
93 		kli = KLCF_COMP(brd, index);
94 		DBG("find_component: brd %p kli %p  request type = 0x%x kli type 0x%x\n", brd, kli, kli->struct_type, KLCF_COMP_TYPE(kli));
95 		if (KLCF_COMP_TYPE(kli) == struct_type)
96 			return kli;
97 	}
98 
99 	/* Didn't find it. */
100 	return (klinfo_t *)NULL;
101 }
102 
103 klinfo_t *
find_first_component(lboard_t * brd,unsigned char struct_type)104 find_first_component(lboard_t *brd, unsigned char struct_type)
105 {
106 	return find_component(brd, (klinfo_t *)NULL, struct_type);
107 }
108 
109 lboard_t *
find_lboard_modslot(lboard_t * start,geoid_t geoid)110 find_lboard_modslot(lboard_t *start, geoid_t geoid)
111 {
112 	/* Search all boards stored on this node. */
113 	while (start) {
114 		if (geo_cmp(start->brd_geoid, geoid))
115 			return start;
116 		start = KLCF_NEXT(start);
117 	}
118 
119 	/* Didn't find it. */
120 	return (lboard_t *)NULL;
121 }
122 
123 lboard_t *
find_lboard_module(lboard_t * start,geoid_t geoid)124 find_lboard_module(lboard_t *start, geoid_t geoid)
125 {
126         /* Search all boards stored on this node. */
127         while (start) {
128                 if (geo_cmp(start->brd_geoid, geoid))
129                         return start;
130                 start = KLCF_NEXT(start);
131         }
132 
133         /* Didn't find it. */
134         return (lboard_t *)NULL;
135 }
136 
137 /*
138  * Convert a NIC name to a name for use in the hardware graph.
139  */
140 void
nic_name_convert(char * old_name,char * new_name)141 nic_name_convert(char *old_name, char *new_name)
142 {
143         int i;
144         char c;
145         char *compare_ptr;
146 
147 	if ((old_name[0] == '\0') || (old_name[1] == '\0')) {
148                 strcpy(new_name, EDGE_LBL_XWIDGET);
149         } else {
150                 for (i = 0; i < strlen(old_name); i++) {
151                         c = old_name[i];
152 
153                         if (isalpha(c))
154                                 new_name[i] = tolower(c);
155                         else if (isdigit(c))
156                                 new_name[i] = c;
157                         else
158                                 new_name[i] = '_';
159                 }
160                 new_name[i] = '\0';
161         }
162 
163         /* XXX -
164          * Since a bunch of boards made it out with weird names like
165          * IO6-fibbbed and IO6P2, we need to look for IO6 in a name and
166          * replace it with "baseio" to avoid confusion in the field.
167 	 * We also have to make sure we don't report media_io instead of
168 	 * baseio.
169          */
170 
171         /* Skip underscores at the beginning of the name */
172         for (compare_ptr = new_name; (*compare_ptr) == '_'; compare_ptr++)
173                 ;
174 
175 	/*
176 	 * Check for some names we need to replace.  Early boards
177 	 * had junk following the name so check only the first
178 	 * characters.
179 	 */
180         if (!strncmp(new_name, "io6", 3) ||
181             !strncmp(new_name, "mio", 3) ||
182 	    !strncmp(new_name, "media_io", 8))
183 		strcpy(new_name, "baseio");
184 	else if (!strncmp(new_name, "divo", 4))
185 		strcpy(new_name, "divo") ;
186 
187 }
188 
189 /*
190  * get_actual_nasid
191  *
192  *	Completely disabled brds have their klconfig on
193  *	some other nasid as they have no memory. But their
194  *	actual nasid is hidden in the klconfig. Use this
195  *	routine to get it. Works for normal boards too.
196  */
197 nasid_t
get_actual_nasid(lboard_t * brd)198 get_actual_nasid(lboard_t *brd)
199 {
200 	klhub_t	*hub ;
201 
202 	if (!brd)
203 		return INVALID_NASID ;
204 
205 	/* find out if we are a completely disabled brd. */
206 
207         hub  = (klhub_t *)find_first_component(brd, KLSTRUCT_HUB);
208 	if (!hub)
209                 return INVALID_NASID ;
210 	if (!(hub->hub_info.flags & KLINFO_ENABLE))	/* disabled node brd */
211 		return hub->hub_info.physid ;
212 	else
213 		return brd->brd_nasid ;
214 }
215 
216 int
xbow_port_io_enabled(nasid_t nasid,int link)217 xbow_port_io_enabled(nasid_t nasid, int link)
218 {
219 	lboard_t *brd;
220 	klxbow_t *xbow_p;
221 
222 	/*
223 	 * look for boards that might contain an xbow or xbridge
224 	 */
225 	brd = find_lboard((lboard_t *)KL_CONFIG_INFO(nasid), KLTYPE_IOBRICK_XBOW);
226 	if (brd == NULL) return 0;
227 
228 	if ((xbow_p = (klxbow_t *)find_component(brd, NULL, KLSTRUCT_XBOW))
229 	    == NULL)
230 	    return 0;
231 
232 	if (!XBOW_PORT_TYPE_IO(xbow_p, link) || !XBOW_PORT_IS_ENABLED(xbow_p, link))
233 	    return 0;
234 
235 	return 1;
236 }
237 
238 void
board_to_path(lboard_t * brd,char * path)239 board_to_path(lboard_t *brd, char *path)
240 {
241 	moduleid_t modnum;
242 	char *board_name;
243 	char buffer[16];
244 
245 	ASSERT(brd);
246 
247 	switch (KLCLASS(brd->brd_type)) {
248 
249 		case KLCLASS_NODE:
250 			board_name = EDGE_LBL_NODE;
251 			break;
252 		case KLCLASS_ROUTER:
253 			if (brd->brd_type == KLTYPE_META_ROUTER) {
254 				board_name = EDGE_LBL_META_ROUTER;
255 				hasmetarouter++;
256 			} else if (brd->brd_type == KLTYPE_REPEATER_ROUTER) {
257 				board_name = EDGE_LBL_REPEATER_ROUTER;
258 				hasmetarouter++;
259 			} else
260 				board_name = EDGE_LBL_ROUTER;
261 			break;
262 		case KLCLASS_MIDPLANE:
263 			board_name = EDGE_LBL_MIDPLANE;
264 			break;
265 		case KLCLASS_IO:
266 			board_name = EDGE_LBL_IO;
267 			break;
268 		case KLCLASS_IOBRICK:
269 			if (brd->brd_type == KLTYPE_PXBRICK)
270 				board_name = EDGE_LBL_PXBRICK;
271 			else if (brd->brd_type == KLTYPE_IXBRICK)
272 				board_name = EDGE_LBL_IXBRICK;
273 			else if (brd->brd_type == KLTYPE_PBRICK)
274 				board_name = EDGE_LBL_PBRICK;
275 			else if (brd->brd_type == KLTYPE_IBRICK)
276 				board_name = EDGE_LBL_IBRICK;
277 			else if (brd->brd_type == KLTYPE_XBRICK)
278 				board_name = EDGE_LBL_XBRICK;
279 			else if (brd->brd_type == KLTYPE_PEBRICK)
280 				board_name = EDGE_LBL_PEBRICK;
281 			else if (brd->brd_type == KLTYPE_OPUSBRICK)
282 				board_name = EDGE_LBL_OPUSBRICK;
283 			else if (brd->brd_type == KLTYPE_CGBRICK)
284 				board_name = EDGE_LBL_CGBRICK;
285 			else
286 				board_name = EDGE_LBL_IOBRICK;
287 			break;
288 		default:
289 			board_name = EDGE_LBL_UNKNOWN;
290 	}
291 
292 	modnum = geo_module(brd->brd_geoid);
293 	memset(buffer, 0, 16);
294 	format_module_id(buffer, modnum, MODULE_FORMAT_BRIEF);
295 	sprintf(path, EDGE_LBL_MODULE "/%s/" EDGE_LBL_SLAB "/%d/%s", buffer, geo_slab(brd->brd_geoid), board_name);
296 }
297 
298 #define MHZ	1000000
299 
300 
301 /* Get the canonical hardware graph name for the given pci component
302  * on the given io board.
303  */
304 void
device_component_canonical_name_get(lboard_t * brd,klinfo_t * component,char * name)305 device_component_canonical_name_get(lboard_t 	*brd,
306 				    klinfo_t 	*component,
307 				    char 	*name)
308 {
309 	slotid_t 	slot;
310 	char 		board_name[20];
311 
312 	ASSERT(brd);
313 
314 	/* Convert the [ CLASS | TYPE ] kind of slotid
315 	 * into a string
316 	 */
317 	slot = brd->brd_slot;
318 
319 	/* Get the io board name  */
320 	if (!brd || (brd->brd_sversion < 2)) {
321 		strcpy(name, EDGE_LBL_XWIDGET);
322 	} else {
323 		nic_name_convert(brd->brd_name, board_name);
324 	}
325 
326 	/* Give out the canonical  name of the pci device*/
327 	sprintf(name,
328 		"/dev/hw/"EDGE_LBL_MODULE "/%x/"EDGE_LBL_SLAB"/%d/"
329 		EDGE_LBL_SLOT"/%s/"EDGE_LBL_PCI"/%d",
330 		geo_module(brd->brd_geoid), geo_slab(brd->brd_geoid),
331 		board_name, KLCF_BRIDGE_W_ID(component));
332 }
333 
334 /*
335  * Get the serial number of the main  component of a board
336  * Returns 0 if a valid serial number is found
337  * 1 otherwise.
338  * Assumptions: Nic manufacturing string  has the following format
339  *			*Serial:<serial_number>;*
340  */
341 static int
component_serial_number_get(lboard_t * board,klconf_off_t mfg_nic_offset,char * serial_number,char * key_pattern)342 component_serial_number_get(lboard_t 		*board,
343 			    klconf_off_t 	mfg_nic_offset,
344 			    char		*serial_number,
345 			    char		*key_pattern)
346 {
347 
348 	char	*mfg_nic_string;
349 	char	*serial_string,*str;
350 	int	i;
351 	char	*serial_pattern = "Serial:";
352 
353 	/* We have an error on a null mfg nic offset */
354 	if (!mfg_nic_offset)
355 		return(1);
356 	/* Get the hub's manufacturing nic information
357 	 * which is in the form of a pre-formatted string
358 	 */
359 	mfg_nic_string =
360 		(char *)NODE_OFFSET_TO_K0(NASID_GET(board),
361 					  mfg_nic_offset);
362 	/* There is no manufacturing nic info */
363 	if (!mfg_nic_string)
364 		return(1);
365 
366 	str = mfg_nic_string;
367 	/* Look for the key pattern first (if it is  specified)
368 	 * and then print the serial number corresponding to that.
369 	 */
370 	if (strcmp(key_pattern,"") &&
371 	    !(str = strstr(mfg_nic_string,key_pattern)))
372 		return(1);
373 
374 	/* There is no serial number info in the manufacturing
375 	 * nic info
376 	 */
377 	if (!(serial_string = strstr(str,serial_pattern)))
378 		return(1);
379 
380 	serial_string = serial_string + strlen(serial_pattern);
381 	/*  Copy the serial number information from the klconfig */
382 	i = 0;
383 	while (serial_string[i] != ';') {
384 		serial_number[i] = serial_string[i];
385 		i++;
386 	}
387 	serial_number[i] = 0;
388 
389 	return(0);
390 }
391 /*
392  * Get the serial number of a board
393  * Returns 0 if a valid serial number is found
394  * 1 otherwise.
395  */
396 
397 int
board_serial_number_get(lboard_t * board,char * serial_number)398 board_serial_number_get(lboard_t *board,char *serial_number)
399 {
400 	ASSERT(board && serial_number);
401 	if (!board || !serial_number)
402 		return(1);
403 
404 	strcpy(serial_number,"");
405 	switch(KLCLASS(board->brd_type)) {
406 	case KLCLASS_CPU: {	/* Node board */
407 		klhub_t *hub;
408 
409 		if (board->brd_type == KLTYPE_TIO) {
410 			printk("*****board_serial_number_get: Need to support TIO.*****\n");
411 			strcpy(serial_number,"");
412 			return(0);
413 		}
414 
415 		/* Get the hub component information */
416 		hub = (klhub_t *)find_first_component(board,
417 						      KLSTRUCT_HUB);
418 		/* If we don't have a hub component on an IP27
419 		 * then we have a weird klconfig.
420 		 */
421 		if (!hub)
422 			return(1);
423 		/* Get the serial number information from
424 		 * the hub's manufacturing nic info
425 		 */
426 		if (component_serial_number_get(board,
427 						hub->hub_mfg_nic,
428 						serial_number,
429 						"IP37"))
430 				return(1);
431 		break;
432 	}
433 	case KLCLASS_IO: {	/* IO board */
434 		if (KLTYPE(board->brd_type) == KLTYPE_TPU) {
435 		/* Special case for TPU boards */
436 			kltpu_t *tpu;
437 
438 			/* Get the tpu component information */
439 			tpu = (kltpu_t *)find_first_component(board,
440 						      KLSTRUCT_TPU);
441 			/* If we don't have a tpu component on a tpu board
442 			 * then we have a weird klconfig.
443 			 */
444 			if (!tpu)
445 				return(1);
446 			/* Get the serial number information from
447 			 * the tpu's manufacturing nic info
448 			 */
449 			if (component_serial_number_get(board,
450 						tpu->tpu_mfg_nic,
451 						serial_number,
452 						""))
453 				return(1);
454 			break;
455 		} else  if ((KLTYPE(board->brd_type) == KLTYPE_GSN_A) ||
456 		            (KLTYPE(board->brd_type) == KLTYPE_GSN_B)) {
457 		/* Special case for GSN boards */
458 			klgsn_t *gsn;
459 
460 			/* Get the gsn component information */
461 			gsn = (klgsn_t *)find_first_component(board,
462 			      ((KLTYPE(board->brd_type) == KLTYPE_GSN_A) ?
463 					KLSTRUCT_GSN_A : KLSTRUCT_GSN_B));
464 			/* If we don't have a gsn component on a gsn board
465 			 * then we have a weird klconfig.
466 			 */
467 			if (!gsn)
468 				return(1);
469 			/* Get the serial number information from
470 			 * the gsn's manufacturing nic info
471 			 */
472 			if (component_serial_number_get(board,
473 						gsn->gsn_mfg_nic,
474 						serial_number,
475 						""))
476 				return(1);
477 			break;
478 		} else {
479 		     	klbri_t	*bridge;
480 
481 			/* Get the bridge component information */
482 			bridge = (klbri_t *)find_first_component(board,
483 							 KLSTRUCT_BRI);
484 			/* If we don't have a bridge component on an IO board
485 			 * then we have a weird klconfig.
486 			 */
487 			if (!bridge)
488 				return(1);
489 			/* Get the serial number information from
490 		 	 * the bridge's manufacturing nic info
491 			 */
492 			if (component_serial_number_get(board,
493 						bridge->bri_mfg_nic,
494 						serial_number,
495 						""))
496 				return(1);
497 			break;
498 		}
499 	}
500 	case KLCLASS_ROUTER: {	/* Router board */
501 		klrou_t *router;
502 
503 		/* Get the router component information */
504 		router = (klrou_t *)find_first_component(board,
505 							 KLSTRUCT_ROU);
506 		/* If we don't have a router component on a router board
507 		 * then we have a weird klconfig.
508 		 */
509 		if (!router)
510 			return(1);
511 		/* Get the serial number information from
512 		 * the router's manufacturing nic info
513 		 */
514 		if (component_serial_number_get(board,
515 						router->rou_mfg_nic,
516 						serial_number,
517 						""))
518 			return(1);
519 		break;
520 	}
521 	case KLCLASS_GFX: {	/* Gfx board */
522 		klgfx_t *graphics;
523 
524 		/* Get the graphics component information */
525 		graphics = (klgfx_t *)find_first_component(board, KLSTRUCT_GFX);
526 		/* If we don't have a gfx component on a gfx board
527 		 * then we have a weird klconfig.
528 		 */
529 		if (!graphics)
530 			return(1);
531 		/* Get the serial number information from
532 		 * the graphics's manufacturing nic info
533 		 */
534 		if (component_serial_number_get(board,
535 						graphics->gfx_mfg_nic,
536 						serial_number,
537 						""))
538 			return(1);
539 		break;
540 	}
541 	default:
542 		strcpy(serial_number,"");
543 		break;
544 	}
545 	return(0);
546 }
547 
548 #include "asm/sn/sn_private.h"
549 
550 /*
551  * Format a module id for printing.
552  */
553 void
format_module_id(char * buffer,moduleid_t m,int fmt)554 format_module_id(char *buffer, moduleid_t m, int fmt)
555 {
556 	int rack, position;
557 	char brickchar;
558 
559 	rack = MODULE_GET_RACK(m);
560 	ASSERT(MODULE_GET_BTYPE(m) < MAX_BRICK_TYPES);
561 	brickchar = MODULE_GET_BTCHAR(m);
562 
563 	position = MODULE_GET_BPOS(m);
564 
565 	if (fmt == MODULE_FORMAT_BRIEF) {
566 	    /* Brief module number format, eg. 002c15 */
567 
568 	    /* Decompress the rack number */
569 	    *buffer++ = '0' + RACK_GET_CLASS(rack);
570 	    *buffer++ = '0' + RACK_GET_GROUP(rack);
571 	    *buffer++ = '0' + RACK_GET_NUM(rack);
572 
573 	    /* Add the brick type */
574 	    *buffer++ = brickchar;
575 	}
576 	else if (fmt == MODULE_FORMAT_LONG) {
577 	    /* Fuller hwgraph format, eg. rack/002/bay/15 */
578 
579 	    strcpy(buffer, EDGE_LBL_RACK "/");  buffer += strlen(buffer);
580 
581 	    *buffer++ = '0' + RACK_GET_CLASS(rack);
582 	    *buffer++ = '0' + RACK_GET_GROUP(rack);
583 	    *buffer++ = '0' + RACK_GET_NUM(rack);
584 
585 	    strcpy(buffer, "/" EDGE_LBL_RPOS "/");  buffer += strlen(buffer);
586 	}
587 
588 	/* Add the bay position, using at least two digits */
589 	if (position < 10)
590 	    *buffer++ = '0';
591 	sprintf(buffer, "%d", position);
592 
593 }
594 
595 /*
596  * Parse a module id, in either brief or long form.
597  * Returns < 0 on error.
598  * The long form does not include a brick type, so it defaults to 0 (CBrick)
599  */
600 int
parse_module_id(char * buffer)601 parse_module_id(char *buffer)
602 {
603 	unsigned int	v, rack, bay, type, form;
604 	moduleid_t	m;
605 	char 		c;
606 
607 	if (strstr(buffer, EDGE_LBL_RACK "/") == buffer) {
608 		form = MODULE_FORMAT_LONG;
609 		buffer += strlen(EDGE_LBL_RACK "/");
610 
611 		/* A long module ID must be exactly 5 non-template chars. */
612 		if (strlen(buffer) != strlen("/" EDGE_LBL_RPOS "/") + 5)
613 			return -1;
614 	}
615 	else {
616 		form = MODULE_FORMAT_BRIEF;
617 
618 		/* A brief module id must be exactly 6 characters */
619 		if (strlen(buffer) != 6)
620 			return -2;
621 	}
622 
623 	/* The rack number must be exactly 3 digits */
624 	if (!(isdigit(buffer[0]) && isdigit(buffer[1]) && isdigit(buffer[2])))
625 		return -3;
626 
627 	rack = 0;
628 	v = *buffer++ - '0';
629 	if (v > RACK_CLASS_MASK(rack) >> RACK_CLASS_SHFT(rack))
630 		return -4;
631 	RACK_ADD_CLASS(rack, v);
632 
633 	v = *buffer++ - '0';
634 	if (v > RACK_GROUP_MASK(rack) >> RACK_GROUP_SHFT(rack))
635 		return -5;
636 	RACK_ADD_GROUP(rack, v);
637 
638 	v = *buffer++ - '0';
639 	/* rack numbers are 1-based */
640 	if (v-1 > RACK_NUM_MASK(rack) >> RACK_NUM_SHFT(rack))
641 		return -6;
642 	RACK_ADD_NUM(rack, v);
643 
644 	if (form == MODULE_FORMAT_BRIEF) {
645 		/* Next should be a module type character.  Accept ucase or lcase. */
646 		c = *buffer++;
647 		if (!isalpha(c))
648 			return -7;
649 
650 		/* strchr() returns a pointer into brick_types[], or NULL */
651 		type = (unsigned int)(strchr(brick_types, tolower(c)) - brick_types);
652 		if (type > MODULE_BTYPE_MASK >> MODULE_BTYPE_SHFT)
653 			return -8;
654 	}
655 	else {
656 		/* Hardcode the module type, and skip over the boilerplate */
657 		type = MODULE_CBRICK;
658 
659 		if (strstr(buffer, "/" EDGE_LBL_RPOS "/") != buffer)
660 			return -9;
661 
662 		buffer += strlen("/" EDGE_LBL_RPOS "/");
663 	}
664 
665 	/* The bay number is last.  Make sure it's exactly two digits */
666 
667 	if (!(isdigit(buffer[0]) && isdigit(buffer[1]) && !buffer[2]))
668 		return -10;
669 
670 	bay = 10 * (buffer[0] - '0') + (buffer[1] - '0');
671 
672 	if (bay > MODULE_BPOS_MASK >> MODULE_BPOS_SHFT)
673 		return -11;
674 
675 	m = RBT_TO_MODULE(rack, bay, type);
676 
677 	/* avoid sign extending the moduleid_t */
678 	return (int)(unsigned short)m;
679 }
680