Home | History | Annotate | Download | only in devfsadm
      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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     22  * Use is subject to license terms.
     23  */
     24 
     25 #include <devfsadm.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <limits.h>
     29 #include <string.h>
     30 #include <unistd.h>
     31 #include <sys/types.h>
     32 #include <sys/stat.h>
     33 #include <strings.h>
     34 
     35 extern char *devfsadm_get_devices_dir();
     36 static int usb_process(di_minor_t minor, di_node_t node);
     37 
     38 static void ugen_create_link(char *p_path, char *node_name,
     39     di_node_t node, di_minor_t minor);
     40 
     41 
     42 /* Rules for creating links */
     43 static devfsadm_create_t usb_cbt[] = {
     44 	{ "usb", NULL, "usb_ac",	DRV_EXACT,
     45 						ILEVEL_0, usb_process },
     46 	{ "usb", NULL, "usb_as",	DRV_EXACT,
     47 						ILEVEL_0, usb_process },
     48 	{ "usb", NULL, "ddivs_usbc",	DRV_EXACT,
     49 						ILEVEL_0, usb_process },
     50 	{ "usb", NULL, "usbvc",		DRV_EXACT,
     51 						ILEVEL_0, usb_process },
     52 	{ "usb", NULL, "hid",		DRV_EXACT,
     53 						ILEVEL_0, usb_process },
     54 	{ "usb", NULL, "hwarc",	DRV_EXACT,
     55 						ILEVEL_0, usb_process },
     56 	{ "usb", NULL, "wusb_ca",	DRV_EXACT,
     57 						ILEVEL_0, usb_process },
     58 	{ "usb", DDI_NT_NEXUS, "hubd",	DRV_EXACT|TYPE_EXACT,
     59 						ILEVEL_0, usb_process },
     60 	{ "usb", DDI_NT_NEXUS, "ohci",	DRV_EXACT|TYPE_EXACT,
     61 						ILEVEL_0, usb_process },
     62 	{ "usb", DDI_NT_NEXUS, "ehci",	DRV_EXACT|TYPE_EXACT,
     63 						ILEVEL_0, usb_process },
     64 	{ "usb", DDI_NT_SCSI_NEXUS, "scsa2usb",	DRV_EXACT|TYPE_EXACT,
     65 						ILEVEL_0, usb_process },
     66 	{ "usb", DDI_NT_UGEN, "scsa2usb",	DRV_EXACT|TYPE_EXACT,
     67 						ILEVEL_0, usb_process },
     68 	{ "usb", DDI_NT_NEXUS, "uhci",	DRV_EXACT|TYPE_EXACT,
     69 						ILEVEL_0, usb_process },
     70 	{ "usb", DDI_NT_UGEN, "ugen",	DRV_EXACT|TYPE_EXACT,
     71 						ILEVEL_0, usb_process },
     72 	{ "usb", DDI_NT_NEXUS, "usb_mid", DRV_EXACT|TYPE_EXACT,
     73 						ILEVEL_0, usb_process },
     74 	{ "usb", DDI_NT_UGEN, "usb_mid", DRV_EXACT|TYPE_EXACT,
     75 						ILEVEL_0, usb_process },
     76 	{ "usb", DDI_NT_PRINTER, "usbprn", DRV_EXACT|TYPE_EXACT,
     77 						ILEVEL_0, usb_process },
     78 	{ "usb", DDI_NT_UGEN, "usbprn", DRV_EXACT|TYPE_EXACT,
     79 						ILEVEL_0, usb_process },
     80 	{ "usb", DDI_NT_NEXUS, "hwahc", DRV_EXACT|TYPE_EXACT,
     81 						ILEVEL_0, usb_process },
     82 };
     83 
     84 /* For debug printing (-V filter) */
     85 static char *debug_mid = "usb_mid";
     86 
     87 DEVFSADM_CREATE_INIT_V0(usb_cbt);
     88 
     89 /* USB device links */
     90 #define	USB_LINK_RE_AUDIO	"^usb/audio[0-9]+$"
     91 #define	USB_LINK_RE_AUDIOMUX	"^usb/audio-mux[0-9]+$"
     92 #define	USB_LINK_RE_AUDIOCTL	"^usb/audio-control[0-9]+$"
     93 #define	USB_LINK_RE_AUDIOSTREAM	"^usb/audio-stream[0-9]+$"
     94 #define	USB_LINK_RE_DDIVS_USBC	"^usb/ddivs_usbc[0-9]+$"
     95 #define	USB_LINK_RE_VIDEO	"^usb/video[0-9]+$"
     96 #define	USB_LINK_RE_VIDEO2	"^video[0-9]+$"
     97 #define	USB_LINK_RE_DEVICE	"^usb/device[0-9]+$"
     98 #define	USB_LINK_RE_HID		"^usb/hid[0-9]+$"
     99 #define	USB_LINK_RE_HUB		"^usb/hub[0-9]+$"
    100 #define	USB_LINK_RE_MASS_STORE	"^usb/mass-storage[0-9]+$"
    101 #define	USB_LINK_RE_UGEN	"^usb/[0-9,a-f]+\\.[0-9,a-f]+/[0-9]+/.+$"
    102 #define	USB_LINK_RE_USBPRN	"^usb/printer[0-9]+$"
    103 #define	USB_LINK_RE_WHOST	"^usb/whost[0-9]+$"
    104 #define	USB_LINK_RE_HWARC	"^usb/hwarc[0-9]+$"
    105 #define	USB_LINK_RE_WUSB_CA	"^usb/wusb_ca[0-9]+$"
    106 
    107 /* Rules for removing links */
    108 static devfsadm_remove_t usb_remove_cbt[] = {
    109 	{ "usb", USB_LINK_RE_AUDIO, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    110 			devfsadm_rm_all },
    111 	{ "usb", USB_LINK_RE_AUDIOMUX, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    112 			devfsadm_rm_all },
    113 	{ "usb", USB_LINK_RE_AUDIOCTL, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    114 			devfsadm_rm_all },
    115 	{ "usb", USB_LINK_RE_AUDIOSTREAM, RM_POST | RM_HOT | RM_ALWAYS,
    116 			ILEVEL_0, devfsadm_rm_all },
    117 	{ "usb", USB_LINK_RE_DDIVS_USBC, RM_POST | RM_HOT | RM_ALWAYS,
    118 			ILEVEL_0, devfsadm_rm_all },
    119 	{ "usb", USB_LINK_RE_VIDEO2, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    120 			devfsadm_rm_all },
    121 	{ "usb", USB_LINK_RE_VIDEO, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    122 			devfsadm_rm_all },
    123 	{ "usb", USB_LINK_RE_DEVICE, RM_POST | RM_HOT, ILEVEL_0,
    124 			devfsadm_rm_all },
    125 	{ "usb", USB_LINK_RE_HID, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    126 			devfsadm_rm_all },
    127 	{ "usb", USB_LINK_RE_HUB, RM_POST | RM_HOT, ILEVEL_0, devfsadm_rm_all },
    128 	{ "usb", USB_LINK_RE_MASS_STORE, RM_POST | RM_HOT | RM_ALWAYS,
    129 			ILEVEL_0, devfsadm_rm_all },
    130 	{ "usb", USB_LINK_RE_UGEN, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    131 			devfsadm_rm_all },
    132 	{ "usb", USB_LINK_RE_USBPRN, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    133 			devfsadm_rm_link },
    134 	{ "usb", USB_LINK_RE_WHOST, RM_POST | RM_HOT, ILEVEL_0,
    135 			devfsadm_rm_all },
    136 	{ "usb", USB_LINK_RE_HWARC, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    137 			devfsadm_rm_all },
    138 	{ "usb", USB_LINK_RE_WUSB_CA, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
    139 			devfsadm_rm_all }
    140 };
    141 
    142 /*
    143  * Rules for different USB devices except ugen which is dynamically
    144  * created
    145  */
    146 static devfsadm_enumerate_t audio_rules[1] =
    147 	{"^usb$/^audio([0-9]+)$", 1, MATCH_ALL};
    148 static devfsadm_enumerate_t audio_mux_rules[1] =
    149 	{"^usb$/^audio-mux([0-9]+)$", 1, MATCH_ALL};
    150 static devfsadm_enumerate_t audio_control_rules[1] =
    151 	{"^usb$/^audio-control([0-9]+)$", 1, MATCH_ALL};
    152 static devfsadm_enumerate_t audio_stream_rules[1] =
    153 	{"^usb$/^audio-stream([0-9]+)$", 1, MATCH_ALL};
    154 static devfsadm_enumerate_t ddivs_usbc_rules[1] =
    155 	{"^usb$/^ddivs_usbc([0-9]+)$", 1, MATCH_ALL};
    156 static devfsadm_enumerate_t video_rules[1] =
    157 	{"^usb$/^video([0-9]+)$", 1, MATCH_ALL};
    158 static devfsadm_enumerate_t device_rules[1] =
    159 	{"^usb$/^device([0-9]+)$", 1, MATCH_ALL};
    160 static devfsadm_enumerate_t hid_rules[1] =
    161 	{"^usb$/^hid([0-9]+)$", 1, MATCH_ALL};
    162 static devfsadm_enumerate_t hub_rules[1] =
    163 	{"^usb$/^hub([0-9]+)$", 1, MATCH_ALL};
    164 static devfsadm_enumerate_t mass_storage_rules[1] =
    165 	{"^usb$/^mass-storage([0-9]+)$", 1, MATCH_ALL};
    166 static devfsadm_enumerate_t usbprn_rules[1] =
    167 	{"^usb$/^printer([0-9]+)$", 1, MATCH_ALL};
    168 static devfsadm_enumerate_t whost_rules[1] =
    169 	{"^usb$/^whost([0-9]+)$", 1, MATCH_ALL};
    170 static devfsadm_enumerate_t hwarc_rules[1] =
    171 	{"^usb$/^hwarc([0-9]+)$", 1, MATCH_ALL};
    172 static devfsadm_enumerate_t wusb_ca_rules[1] =
    173 	{"^usb$/^wusb_ca([0-9]+)$", 1, MATCH_ALL};
    174 
    175 DEVFSADM_REMOVE_INIT_V0(usb_remove_cbt);
    176 
    177 int
    178 minor_init(void)
    179 {
    180 	devfsadm_print(debug_mid, "usb_link: minor_init\n");
    181 	return (DEVFSADM_SUCCESS);
    182 }
    183 
    184 int
    185 minor_fini(void)
    186 {
    187 	devfsadm_print(debug_mid, "usb_link: minor_fini\n");
    188 	return (DEVFSADM_SUCCESS);
    189 }
    190 
    191 typedef enum {
    192 	DRIVER_HUBD	= 0,
    193 	DRIVER_OHCI	= 1,
    194 	DRIVER_EHCI	= 2,
    195 	DRIVER_UHCI	= 3,
    196 	DRIVER_USB_AC	= 4,
    197 	DRIVER_USB_AS	= 5,
    198 	DRIVER_HID	= 6,
    199 	DRIVER_USB_MID	= 7,
    200 	DRIVER_DDIVS_USBC = 8,
    201 	DRIVER_SCSA2USB = 9,
    202 	DRIVER_USBPRN	= 10,
    203 	DRIVER_UGEN	= 11,
    204 	DRIVER_VIDEO	= 12,
    205 	DRIVER_HWAHC	= 13,
    206 	DRIVER_HWARC	= 14,
    207 	DRIVER_WUSB_CA	= 15,
    208 	DRIVER_UNKNOWN	= 16
    209 } driver_defs_t;
    210 
    211 typedef struct {
    212 	char	*driver_name;
    213 	int	index;
    214 } driver_name_table_entry_t;
    215 
    216 driver_name_table_entry_t driver_name_table[] = {
    217 	{ "hubd",	DRIVER_HUBD },
    218 	{ "ohci",	DRIVER_OHCI },
    219 	{ "ehci",	DRIVER_EHCI },
    220 	{ "uhci",	DRIVER_UHCI },
    221 	{ "usb_ac",	DRIVER_USB_AC },
    222 	{ "usb_as",	DRIVER_USB_AS },
    223 	{ "hid",	DRIVER_HID },
    224 	{ "usb_mid",	DRIVER_USB_MID },
    225 	{ "ddivs_usbc",	DRIVER_DDIVS_USBC },
    226 	{ "scsa2usb",	DRIVER_SCSA2USB },
    227 	{ "usbprn",	DRIVER_USBPRN },
    228 	{ "ugen",	DRIVER_UGEN },
    229 	{ "usbvc",	DRIVER_VIDEO },
    230 	{ "hwahc",	DRIVER_HWAHC },
    231 	{ "hwarc",	DRIVER_HWARC },
    232 	{ "wusb_ca",	DRIVER_WUSB_CA },
    233 	{ NULL,		DRIVER_UNKNOWN }
    234 };
    235 
    236 /*
    237  * This function is called for every usb minor node.
    238  * Calls enumerate to assign a logical usb id, and then
    239  * devfsadm_mklink to make the link.
    240  */
    241 static int
    242 usb_process(di_minor_t minor, di_node_t node)
    243 {
    244 	devfsadm_enumerate_t rules[1];
    245 	char *l_path, *p_path, *buf, *devfspath;
    246 	char *minor_nm, *drvr_nm, *name = (char *)NULL;
    247 	int i, index;
    248 	int flags = 0;
    249 	int create_secondary_link = 0;
    250 
    251 	minor_nm = di_minor_name(minor);
    252 	drvr_nm = di_driver_name(node);
    253 	if ((minor_nm == NULL) || (drvr_nm == NULL)) {
    254 		return (DEVFSADM_CONTINUE);
    255 	}
    256 
    257 	devfsadm_print(debug_mid, "usb_process: minor=%s node=%s type=%s\n",
    258 	    minor_nm, di_node_name(node), di_minor_nodetype(minor));
    259 
    260 	devfspath = di_devfs_path(node);
    261 	if (devfspath == NULL) {
    262 		devfsadm_print(debug_mid,
    263 		    "USB_process: devfspath is	NULL\n");
    264 		return (DEVFSADM_CONTINUE);
    265 	}
    266 
    267 	l_path = (char *)malloc(PATH_MAX);
    268 	if (l_path == NULL) {
    269 		di_devfs_path_free(devfspath);
    270 		devfsadm_print(debug_mid, "usb_process: malloc() failed\n");
    271 		return (DEVFSADM_CONTINUE);
    272 	}
    273 
    274 	p_path = (char *)malloc(PATH_MAX);
    275 	if (p_path == NULL) {
    276 		devfsadm_print(debug_mid, "usb_process: malloc() failed\n");
    277 		di_devfs_path_free(devfspath);
    278 		free(l_path);
    279 		return (DEVFSADM_CONTINUE);
    280 	}
    281 
    282 	(void) strcpy(p_path, devfspath);
    283 	(void) strcat(p_path, ":");
    284 	(void) strcat(p_path, minor_nm);
    285 	di_devfs_path_free(devfspath);
    286 
    287 	devfsadm_print(debug_mid, "usb_process: path %s\n", p_path);
    288 
    289 	for (i = 0; ; i++) {
    290 		if ((driver_name_table[i].driver_name == NULL) ||
    291 		    (strcmp(drvr_nm, driver_name_table[i].driver_name) == 0)) {
    292 			index = driver_name_table[i].index;
    293 			break;
    294 		}
    295 	}
    296 
    297 	if (strcmp(di_minor_nodetype(minor), DDI_NT_UGEN) == 0) {
    298 		ugen_create_link(p_path, minor_nm, node, minor);
    299 		free(l_path);
    300 		free(p_path);
    301 		return (DEVFSADM_CONTINUE);
    302 	}
    303 
    304 	/* Figure out which rules to apply */
    305 	switch (index) {
    306 	case DRIVER_HUBD:
    307 	case DRIVER_OHCI:
    308 	case DRIVER_EHCI:
    309 	case DRIVER_UHCI:
    310 		rules[0] = hub_rules[0];	/* For HUBs */
    311 		name = "hub";
    312 
    313 		break;
    314 	case DRIVER_USB_AC:
    315 		if (strcmp(minor_nm, "sound,audio") == 0) {
    316 			rules[0] = audio_rules[0];
    317 			name = "audio";		/* For audio */
    318 			create_secondary_link = 1;
    319 		} else if (strcmp(minor_nm, "sound,audioctl") == 0) {
    320 			rules[0] = audio_control_rules[0];
    321 			name = "audio-control";		/* For audio */
    322 			create_secondary_link = 1;
    323 		} else if (strcmp(minor_nm, "mux") == 0) {
    324 			rules[0] = audio_mux_rules[0];
    325 			name = "audio-mux";		/* For audio */
    326 		} else {
    327 			free(l_path);
    328 			free(p_path);
    329 			return (DEVFSADM_CONTINUE);
    330 		}
    331 		break;
    332 	case DRIVER_USB_AS:
    333 		rules[0] = audio_stream_rules[0];
    334 		name = "audio-stream";		/* For audio */
    335 		break;
    336 	case DRIVER_VIDEO:
    337 		rules[0] = video_rules[0];
    338 		name = "video";			/* For video */
    339 		create_secondary_link = 1;
    340 		break;
    341 	case DRIVER_HID:
    342 		rules[0] = hid_rules[0];
    343 		name = "hid";			/* For HIDs */
    344 		break;
    345 	case DRIVER_USB_MID:
    346 		rules[0] = device_rules[0];
    347 		name = "device";		/* For other USB devices */
    348 		break;
    349 	case DRIVER_DDIVS_USBC:
    350 		rules[0] = ddivs_usbc_rules[0];
    351 		name = "device";		/* For other USB devices */
    352 		break;
    353 	case DRIVER_SCSA2USB:
    354 		rules[0] = mass_storage_rules[0];
    355 		name = "mass-storage";		/* For mass-storage devices */
    356 		break;
    357 	case DRIVER_USBPRN:
    358 		rules[0] = usbprn_rules[0];
    359 		name = "printer";
    360 		break;
    361 	case DRIVER_HWAHC:
    362 		if (strcmp(minor_nm, "hwahc") == 0) {
    363 			rules[0] = whost_rules[0];
    364 			name = "whost";		/* For HWA HC */
    365 		} else if (strcmp(minor_nm, "hubd") == 0) {
    366 			rules[0] = hub_rules[0];
    367 			name = "hub";		/* For HWA HC */
    368 		} else {
    369 			free(l_path);
    370 			free(p_path);
    371 			return (DEVFSADM_CONTINUE);
    372 		}
    373 		break;
    374 	case DRIVER_HWARC:
    375 		rules[0] = hwarc_rules[0];
    376 		name = "hwarc";		/* For UWB HWA Radio Controllers */
    377 		break;
    378 	case DRIVER_WUSB_CA:
    379 		rules[0] = wusb_ca_rules[0];
    380 		name = "wusb_ca";	/* for wusb cable association */
    381 		break;
    382 	default:
    383 		devfsadm_print(debug_mid, "usb_process: unknown driver=%s\n",
    384 		    drvr_nm);
    385 		free(l_path);
    386 		free(p_path);
    387 		return (DEVFSADM_CONTINUE);
    388 	}
    389 
    390 	/*
    391 	 *  build the physical path from the components.
    392 	 *  find the logical usb id, and stuff it in buf
    393 	 */
    394 	if (devfsadm_enumerate_int(p_path, 0, &buf, rules, 1)) {
    395 		devfsadm_print(debug_mid, "usb_process: exit/continue\n");
    396 		free(l_path);
    397 		free(p_path);
    398 		return (DEVFSADM_CONTINUE);
    399 	}
    400 
    401 	(void) snprintf(l_path, PATH_MAX, "usb/%s%s", name, buf);
    402 
    403 	devfsadm_print(debug_mid, "usb_process: p_path=%s buf=%s\n",
    404 	    p_path, buf);
    405 
    406 	free(buf);
    407 
    408 	devfsadm_print(debug_mid, "mklink %s -> %s\n", l_path, p_path);
    409 
    410 	(void) devfsadm_mklink(l_path, node, minor, flags);
    411 
    412 	if (create_secondary_link) {
    413 		/*
    414 		 * Create secondary links to make newly hotplugged
    415 		 * usb audio device the primary device.
    416 		 */
    417 		if (strcmp(name, "audio") == 0) {
    418 			(void) devfsadm_secondary_link("audio", l_path, 0);
    419 		} else if (strcmp(name, "audio-control") == 0) {
    420 			(void) devfsadm_secondary_link("audioctl", l_path, 0);
    421 		} else if (strcmp(name, "video") == 0) {
    422 			(void) devfsadm_secondary_link(l_path + 4, l_path, 0);
    423 		}
    424 	}
    425 
    426 	free(p_path);
    427 	free(l_path);
    428 
    429 	return (DEVFSADM_CONTINUE);
    430 }
    431 
    432 static void
    433 ugen_create_link(char *p_path, char *node_name,
    434     di_node_t node, di_minor_t minor)
    435 {
    436 	char *buf, s[MAXPATHLEN];
    437 	char *lasts = s;
    438 	char *vid, *pid;
    439 	char *minor_name;
    440 	char ugen_RE[128];
    441 	devfsadm_enumerate_t ugen_rules[1];
    442 	char l_path[PATH_MAX];
    443 	int flags = 0;
    444 
    445 	devfsadm_print(debug_mid, "ugen_create_link: p_path=%s name=%s\n",
    446 	    p_path, node_name);
    447 
    448 	(void) strlcpy(s, node_name, sizeof (s));
    449 
    450 	/* get vid, pid and minor name strings */
    451 	vid = strtok_r(lasts, ".", &lasts);
    452 	pid = strtok_r(NULL, ".", &lasts);
    453 	minor_name = lasts;
    454 
    455 	if ((vid == NULL) || (pid == NULL) || (minor_name == NULL)) {
    456 		return;
    457 	}
    458 
    459 	/* create regular expression contain vid and pid */
    460 	(void) snprintf(ugen_RE, sizeof (ugen_RE),
    461 	    "^usb$/^%s\\.%s$/^([0-9]+)$", vid, pid);
    462 	devfsadm_print(debug_mid,
    463 	    "ugen_create_link: ugen_RE=%s minor_name=%s\n",
    464 	    ugen_RE, minor_name);
    465 
    466 	bzero(ugen_rules, sizeof (ugen_rules));
    467 
    468 	ugen_rules[0].re = ugen_RE;
    469 	ugen_rules[0].subexp = 1;
    470 	ugen_rules[0].flags = MATCH_ADDR;
    471 
    472 	/*
    473 	 *  build the physical path from the components.
    474 	 *  find the logical usb id, and stuff it in buf
    475 	 */
    476 	if (devfsadm_enumerate_int(p_path, 0, &buf, ugen_rules, 1)) {
    477 		devfsadm_print(debug_mid, "ugen_create_link: exit/continue\n");
    478 		return;
    479 	}
    480 
    481 	(void) snprintf(l_path, sizeof (l_path), "usb/%s.%s/%s/%s",
    482 	    vid, pid, buf, minor_name);
    483 
    484 	devfsadm_print(debug_mid, "mklink %s -> %s\n", l_path, p_path);
    485 
    486 	(void) devfsadm_mklink(l_path, node, minor, flags);
    487 
    488 	free(buf);
    489 }
    490