1*26947304SEvan Yan /* 2*26947304SEvan Yan * CDDL HEADER START 3*26947304SEvan Yan * 4*26947304SEvan Yan * The contents of this file are subject to the terms of the 5*26947304SEvan Yan * Common Development and Distribution License (the "License"). 6*26947304SEvan Yan * You may not use this file except in compliance with the License. 7*26947304SEvan Yan * 8*26947304SEvan Yan * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9*26947304SEvan Yan * or http://www.opensolaris.org/os/licensing. 10*26947304SEvan Yan * See the License for the specific language governing permissions 11*26947304SEvan Yan * and limitations under the License. 12*26947304SEvan Yan * 13*26947304SEvan Yan * When distributing Covered Code, include this CDDL HEADER in each 14*26947304SEvan Yan * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15*26947304SEvan Yan * If applicable, add the following below this CDDL HEADER, with the 16*26947304SEvan Yan * fields enclosed by brackets "[]" replaced with your own identifying 17*26947304SEvan Yan * information: Portions Copyright [yyyy] [name of copyright owner] 18*26947304SEvan Yan * 19*26947304SEvan Yan * CDDL HEADER END 20*26947304SEvan Yan */ 21*26947304SEvan Yan 22*26947304SEvan Yan /* 23*26947304SEvan Yan * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24*26947304SEvan Yan * Use is subject to license terms. 25*26947304SEvan Yan */ 26*26947304SEvan Yan 27*26947304SEvan Yan #include <stdio.h> 28*26947304SEvan Yan #include <stdlib.h> 29*26947304SEvan Yan #include <unistd.h> 30*26947304SEvan Yan #include <errno.h> 31*26947304SEvan Yan #include <string.h> 32*26947304SEvan Yan #include <pthread.h> 33*26947304SEvan Yan #include <alloca.h> 34*26947304SEvan Yan #include <libnvpair.h> 35*26947304SEvan Yan #include <libhotplug.h> 36*26947304SEvan Yan #include <libhotplug_impl.h> 37*26947304SEvan Yan #include <sys/types.h> 38*26947304SEvan Yan #include <sys/sunddi.h> 39*26947304SEvan Yan #include <sys/ddi_hp.h> 40*26947304SEvan Yan #include <sys/modctl.h> 41*26947304SEvan Yan #include "hotplugd_impl.h" 42*26947304SEvan Yan 43*26947304SEvan Yan /* 44*26947304SEvan Yan * All operations affecting kernel state are serialized. 45*26947304SEvan Yan */ 46*26947304SEvan Yan static pthread_mutex_t hotplug_lock = PTHREAD_MUTEX_INITIALIZER; 47*26947304SEvan Yan 48*26947304SEvan Yan /* 49*26947304SEvan Yan * Local functions. 50*26947304SEvan Yan */ 51*26947304SEvan Yan static boolean_t check_rcm_required(hp_node_t, int); 52*26947304SEvan Yan static int pack_properties(const char *, ddi_hp_property_t *); 53*26947304SEvan Yan static void unpack_properties(ddi_hp_property_t *, char **); 54*26947304SEvan Yan static void free_properties(ddi_hp_property_t *); 55*26947304SEvan Yan 56*26947304SEvan Yan /* 57*26947304SEvan Yan * changestate() 58*26947304SEvan Yan * 59*26947304SEvan Yan * Perform a state change operation. 60*26947304SEvan Yan * 61*26947304SEvan Yan * NOTE: all operations are serialized, using a global lock. 62*26947304SEvan Yan */ 63*26947304SEvan Yan int 64*26947304SEvan Yan changestate(const char *path, const char *connection, int state, uint_t flags, 65*26947304SEvan Yan int *old_statep, hp_node_t *resultsp) 66*26947304SEvan Yan { 67*26947304SEvan Yan hp_node_t root = NULL; 68*26947304SEvan Yan char **rsrcs = NULL; 69*26947304SEvan Yan boolean_t use_rcm = B_FALSE; 70*26947304SEvan Yan int rv; 71*26947304SEvan Yan 72*26947304SEvan Yan dprintf("changestate(path=%s, connection=%s, state=0x%x, flags=0x%x)\n", 73*26947304SEvan Yan path, connection, state, flags); 74*26947304SEvan Yan 75*26947304SEvan Yan /* Initialize results */ 76*26947304SEvan Yan *resultsp = NULL; 77*26947304SEvan Yan *old_statep = -1; 78*26947304SEvan Yan 79*26947304SEvan Yan (void) pthread_mutex_lock(&hotplug_lock); 80*26947304SEvan Yan 81*26947304SEvan Yan /* Get an information snapshot, without usage details */ 82*26947304SEvan Yan if ((rv = getinfo(path, connection, 0, &root)) != 0) { 83*26947304SEvan Yan (void) pthread_mutex_unlock(&hotplug_lock); 84*26947304SEvan Yan dprintf("changestate: getinfo() failed (%s)\n", strerror(rv)); 85*26947304SEvan Yan return (rv); 86*26947304SEvan Yan } 87*26947304SEvan Yan 88*26947304SEvan Yan /* Record current state (used in hotplugd_door.c for auditing) */ 89*26947304SEvan Yan *old_statep = hp_state(root); 90*26947304SEvan Yan 91*26947304SEvan Yan /* Check if RCM interactions are required */ 92*26947304SEvan Yan use_rcm = check_rcm_required(root, state); 93*26947304SEvan Yan 94*26947304SEvan Yan /* If RCM is required, perform RCM offline */ 95*26947304SEvan Yan if (use_rcm) { 96*26947304SEvan Yan 97*26947304SEvan Yan dprintf("changestate: RCM offline is required.\n"); 98*26947304SEvan Yan 99*26947304SEvan Yan /* Get RCM resources */ 100*26947304SEvan Yan if ((rv = rcm_resources(root, &rsrcs)) != 0) { 101*26947304SEvan Yan dprintf("changestate: rcm_resources() failed.\n"); 102*26947304SEvan Yan (void) pthread_mutex_unlock(&hotplug_lock); 103*26947304SEvan Yan hp_fini(root); 104*26947304SEvan Yan return (rv); 105*26947304SEvan Yan } 106*26947304SEvan Yan 107*26947304SEvan Yan /* Request RCM offline */ 108*26947304SEvan Yan if ((rsrcs != NULL) && 109*26947304SEvan Yan ((rv = rcm_offline(rsrcs, flags, root)) != 0)) { 110*26947304SEvan Yan dprintf("changestate: rcm_offline() failed.\n"); 111*26947304SEvan Yan rcm_online(rsrcs); 112*26947304SEvan Yan (void) pthread_mutex_unlock(&hotplug_lock); 113*26947304SEvan Yan free_rcm_resources(rsrcs); 114*26947304SEvan Yan *resultsp = root; 115*26947304SEvan Yan return (rv); 116*26947304SEvan Yan } 117*26947304SEvan Yan } 118*26947304SEvan Yan 119*26947304SEvan Yan /* The information snapshot is no longer needed */ 120*26947304SEvan Yan hp_fini(root); 121*26947304SEvan Yan 122*26947304SEvan Yan /* Stop now if QUERY flag was specified */ 123*26947304SEvan Yan if (flags & HPQUERY) { 124*26947304SEvan Yan dprintf("changestate: operation was QUERY only.\n"); 125*26947304SEvan Yan rcm_online(rsrcs); 126*26947304SEvan Yan (void) pthread_mutex_unlock(&hotplug_lock); 127*26947304SEvan Yan free_rcm_resources(rsrcs); 128*26947304SEvan Yan return (0); 129*26947304SEvan Yan } 130*26947304SEvan Yan 131*26947304SEvan Yan /* Do state change in kernel */ 132*26947304SEvan Yan rv = 0; 133*26947304SEvan Yan if (modctl(MODHPOPS, MODHPOPS_CHANGE_STATE, path, connection, state)) 134*26947304SEvan Yan rv = errno; 135*26947304SEvan Yan dprintf("changestate: modctl(MODHPOPS_CHANGE_STATE) = %d.\n", rv); 136*26947304SEvan Yan 137*26947304SEvan Yan /* 138*26947304SEvan Yan * If RCM is required, then perform an RCM online or RCM remove 139*26947304SEvan Yan * operation. Which depends upon if modctl succeeded or failed. 140*26947304SEvan Yan */ 141*26947304SEvan Yan if (use_rcm && (rsrcs != NULL)) { 142*26947304SEvan Yan 143*26947304SEvan Yan /* RCM online if failure, or RCM remove if successful */ 144*26947304SEvan Yan if (rv == 0) 145*26947304SEvan Yan rcm_remove(rsrcs); 146*26947304SEvan Yan else 147*26947304SEvan Yan rcm_online(rsrcs); 148*26947304SEvan Yan 149*26947304SEvan Yan /* RCM resources no longer required */ 150*26947304SEvan Yan free_rcm_resources(rsrcs); 151*26947304SEvan Yan } 152*26947304SEvan Yan 153*26947304SEvan Yan (void) pthread_mutex_unlock(&hotplug_lock); 154*26947304SEvan Yan 155*26947304SEvan Yan *resultsp = NULL; 156*26947304SEvan Yan return (rv); 157*26947304SEvan Yan } 158*26947304SEvan Yan 159*26947304SEvan Yan /* 160*26947304SEvan Yan * private_options() 161*26947304SEvan Yan * 162*26947304SEvan Yan * Implement set/get of bus private options. 163*26947304SEvan Yan */ 164*26947304SEvan Yan int 165*26947304SEvan Yan private_options(const char *path, const char *connection, hp_cmd_t cmd, 166*26947304SEvan Yan const char *options, char **resultsp) 167*26947304SEvan Yan { 168*26947304SEvan Yan ddi_hp_property_t prop; 169*26947304SEvan Yan ddi_hp_property_t results; 170*26947304SEvan Yan char *values = NULL; 171*26947304SEvan Yan int rv; 172*26947304SEvan Yan 173*26947304SEvan Yan dprintf("private_options(path=%s, connection=%s, options='%s')\n", 174*26947304SEvan Yan path, connection, options); 175*26947304SEvan Yan 176*26947304SEvan Yan /* Initialize property arguments */ 177*26947304SEvan Yan if ((rv = pack_properties(options, &prop)) != 0) { 178*26947304SEvan Yan dprintf("private_options: failed to pack properties.\n"); 179*26947304SEvan Yan return (rv); 180*26947304SEvan Yan } 181*26947304SEvan Yan 182*26947304SEvan Yan /* Initialize results */ 183*26947304SEvan Yan (void) memset(&results, 0, sizeof (ddi_hp_property_t)); 184*26947304SEvan Yan results.buf_size = HP_PRIVATE_BUF_SZ; 185*26947304SEvan Yan results.nvlist_buf = (char *)calloc(1, HP_PRIVATE_BUF_SZ); 186*26947304SEvan Yan if (results.nvlist_buf == NULL) { 187*26947304SEvan Yan dprintf("private_options: failed to allocate buffer.\n"); 188*26947304SEvan Yan free_properties(&prop); 189*26947304SEvan Yan return (ENOMEM); 190*26947304SEvan Yan } 191*26947304SEvan Yan 192*26947304SEvan Yan /* Lock hotplug */ 193*26947304SEvan Yan (void) pthread_mutex_lock(&hotplug_lock); 194*26947304SEvan Yan 195*26947304SEvan Yan /* Perform the command */ 196*26947304SEvan Yan rv = 0; 197*26947304SEvan Yan if (cmd == HP_CMD_GETPRIVATE) { 198*26947304SEvan Yan if (modctl(MODHPOPS, MODHPOPS_BUS_GET, path, connection, 199*26947304SEvan Yan &prop, &results)) 200*26947304SEvan Yan rv = errno; 201*26947304SEvan Yan dprintf("private_options: modctl(MODHPOPS_BUS_GET) = %d\n", rv); 202*26947304SEvan Yan } else { 203*26947304SEvan Yan if (modctl(MODHPOPS, MODHPOPS_BUS_SET, path, connection, 204*26947304SEvan Yan &prop, &results)) 205*26947304SEvan Yan rv = errno; 206*26947304SEvan Yan dprintf("private_options: modctl(MODHPOPS_BUS_SET) = %d\n", rv); 207*26947304SEvan Yan } 208*26947304SEvan Yan 209*26947304SEvan Yan /* Unlock hotplug */ 210*26947304SEvan Yan (void) pthread_mutex_unlock(&hotplug_lock); 211*26947304SEvan Yan 212*26947304SEvan Yan /* Parse results */ 213*26947304SEvan Yan if (rv == 0) { 214*26947304SEvan Yan unpack_properties(&results, &values); 215*26947304SEvan Yan *resultsp = values; 216*26947304SEvan Yan } 217*26947304SEvan Yan 218*26947304SEvan Yan /* Cleanup */ 219*26947304SEvan Yan free_properties(&prop); 220*26947304SEvan Yan free_properties(&results); 221*26947304SEvan Yan 222*26947304SEvan Yan return (rv); 223*26947304SEvan Yan } 224*26947304SEvan Yan 225*26947304SEvan Yan /* 226*26947304SEvan Yan * check_rcm_required() 227*26947304SEvan Yan * 228*26947304SEvan Yan * Given the root of a changestate operation and the target 229*26947304SEvan Yan * state, determine if RCM interactions will be required. 230*26947304SEvan Yan */ 231*26947304SEvan Yan static boolean_t 232*26947304SEvan Yan check_rcm_required(hp_node_t root, int target_state) 233*26947304SEvan Yan { 234*26947304SEvan Yan /* 235*26947304SEvan Yan * RCM is required when transitioning an ENABLED 236*26947304SEvan Yan * connector to a non-ENABLED state. 237*26947304SEvan Yan */ 238*26947304SEvan Yan if ((root->hp_type == HP_NODE_CONNECTOR) && 239*26947304SEvan Yan HP_IS_ENABLED(root->hp_state) && !HP_IS_ENABLED(target_state)) 240*26947304SEvan Yan return (B_TRUE); 241*26947304SEvan Yan 242*26947304SEvan Yan /* 243*26947304SEvan Yan * RCM is required when transitioning an OPERATIONAL 244*26947304SEvan Yan * port to a non-OPERATIONAL state. 245*26947304SEvan Yan */ 246*26947304SEvan Yan if ((root->hp_type == HP_NODE_PORT) && 247*26947304SEvan Yan HP_IS_ONLINE(root->hp_state) && HP_IS_OFFLINE(target_state)) 248*26947304SEvan Yan return (B_TRUE); 249*26947304SEvan Yan 250*26947304SEvan Yan /* RCM is not required in other cases */ 251*26947304SEvan Yan return (B_FALSE); 252*26947304SEvan Yan } 253*26947304SEvan Yan 254*26947304SEvan Yan /* 255*26947304SEvan Yan * pack_properties() 256*26947304SEvan Yan * 257*26947304SEvan Yan * Given a specified set/get command and an options string, 258*26947304SEvan Yan * construct the structure containing a packed nvlist that 259*26947304SEvan Yan * contains the specified options. 260*26947304SEvan Yan */ 261*26947304SEvan Yan static int 262*26947304SEvan Yan pack_properties(const char *options, ddi_hp_property_t *prop) 263*26947304SEvan Yan { 264*26947304SEvan Yan nvlist_t *nvl; 265*26947304SEvan Yan char *buf, *tmp, *name, *value, *next; 266*26947304SEvan Yan size_t len; 267*26947304SEvan Yan 268*26947304SEvan Yan /* Initialize results */ 269*26947304SEvan Yan (void) memset(prop, 0, sizeof (ddi_hp_property_t)); 270*26947304SEvan Yan 271*26947304SEvan Yan /* Do nothing if options string is empty */ 272*26947304SEvan Yan if ((len = strlen(options)) == 0) { 273*26947304SEvan Yan dprintf("pack_properties: options string is empty.\n"); 274*26947304SEvan Yan return (ENOENT); 275*26947304SEvan Yan } 276*26947304SEvan Yan 277*26947304SEvan Yan /* Avoid modifying the input string by using a copy on the stack */ 278*26947304SEvan Yan if ((tmp = (char *)alloca(len + 1)) == NULL) { 279*26947304SEvan Yan log_err("Failed to allocate buffer for private options.\n"); 280*26947304SEvan Yan return (ENOMEM); 281*26947304SEvan Yan } 282*26947304SEvan Yan (void) strlcpy(tmp, options, len + 1); 283*26947304SEvan Yan 284*26947304SEvan Yan /* Allocate the nvlist */ 285*26947304SEvan Yan if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { 286*26947304SEvan Yan log_err("Failed to allocate private options nvlist.\n"); 287*26947304SEvan Yan return (ENOMEM); 288*26947304SEvan Yan } 289*26947304SEvan Yan 290*26947304SEvan Yan /* Add each option from the string */ 291*26947304SEvan Yan for (name = tmp; name != NULL; name = next) { 292*26947304SEvan Yan 293*26947304SEvan Yan /* Isolate current name/value, and locate the next */ 294*26947304SEvan Yan if ((next = strchr(name, ',')) != NULL) { 295*26947304SEvan Yan *next = '\0'; 296*26947304SEvan Yan next++; 297*26947304SEvan Yan } 298*26947304SEvan Yan 299*26947304SEvan Yan /* Split current name/value pair */ 300*26947304SEvan Yan if ((value = strchr(name, '=')) != NULL) { 301*26947304SEvan Yan *value = '\0'; 302*26947304SEvan Yan value++; 303*26947304SEvan Yan } else { 304*26947304SEvan Yan value = ""; 305*26947304SEvan Yan } 306*26947304SEvan Yan 307*26947304SEvan Yan /* Add the option to the nvlist */ 308*26947304SEvan Yan if (nvlist_add_string(nvl, name, value) != 0) { 309*26947304SEvan Yan log_err("Failed to add private option to nvlist.\n"); 310*26947304SEvan Yan nvlist_free(nvl); 311*26947304SEvan Yan return (EFAULT); 312*26947304SEvan Yan } 313*26947304SEvan Yan } 314*26947304SEvan Yan 315*26947304SEvan Yan /* Pack the nvlist */ 316*26947304SEvan Yan len = 0; 317*26947304SEvan Yan buf = NULL; 318*26947304SEvan Yan if (nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0) != 0) { 319*26947304SEvan Yan log_err("Failed to pack private options nvlist.\n"); 320*26947304SEvan Yan nvlist_free(nvl); 321*26947304SEvan Yan return (EFAULT); 322*26947304SEvan Yan } 323*26947304SEvan Yan 324*26947304SEvan Yan /* Save results */ 325*26947304SEvan Yan prop->nvlist_buf = buf; 326*26947304SEvan Yan prop->buf_size = len; 327*26947304SEvan Yan 328*26947304SEvan Yan /* The nvlist is no longer needed */ 329*26947304SEvan Yan nvlist_free(nvl); 330*26947304SEvan Yan 331*26947304SEvan Yan return (0); 332*26947304SEvan Yan } 333*26947304SEvan Yan 334*26947304SEvan Yan /* 335*26947304SEvan Yan * unpack_properties() 336*26947304SEvan Yan * 337*26947304SEvan Yan * Given a structure possibly containing a packed nvlist of 338*26947304SEvan Yan * bus private options, unpack the nvlist and expand its 339*26947304SEvan Yan * contents into an options string. 340*26947304SEvan Yan */ 341*26947304SEvan Yan static void 342*26947304SEvan Yan unpack_properties(ddi_hp_property_t *prop, char **optionsp) 343*26947304SEvan Yan { 344*26947304SEvan Yan nvlist_t *nvl = NULL; 345*26947304SEvan Yan nvpair_t *nvp; 346*26947304SEvan Yan boolean_t first_flag; 347*26947304SEvan Yan char *name, *value, *options; 348*26947304SEvan Yan size_t len; 349*26947304SEvan Yan 350*26947304SEvan Yan /* Initialize results */ 351*26947304SEvan Yan *optionsp = NULL; 352*26947304SEvan Yan 353*26947304SEvan Yan /* Do nothing if properties do not exist */ 354*26947304SEvan Yan if ((prop->nvlist_buf == NULL) || (prop->buf_size == 0)) { 355*26947304SEvan Yan dprintf("unpack_properties: no properties exist.\n"); 356*26947304SEvan Yan return; 357*26947304SEvan Yan } 358*26947304SEvan Yan 359*26947304SEvan Yan /* Unpack the nvlist */ 360*26947304SEvan Yan if (nvlist_unpack(prop->nvlist_buf, prop->buf_size, &nvl, 0) != 0) { 361*26947304SEvan Yan log_err("Failed to unpack private options nvlist.\n"); 362*26947304SEvan Yan return; 363*26947304SEvan Yan } 364*26947304SEvan Yan 365*26947304SEvan Yan /* Compute the size of the options string */ 366*26947304SEvan Yan for (len = 0, nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) { 367*26947304SEvan Yan 368*26947304SEvan Yan name = nvpair_name(nvp); 369*26947304SEvan Yan 370*26947304SEvan Yan /* Skip the command, and anything not a string */ 371*26947304SEvan Yan if ((strcmp(name, "cmd") == 0) || 372*26947304SEvan Yan (nvpair_type(nvp) != DATA_TYPE_STRING)) 373*26947304SEvan Yan continue; 374*26947304SEvan Yan 375*26947304SEvan Yan (void) nvpair_value_string(nvp, &value); 376*26947304SEvan Yan 377*26947304SEvan Yan /* Account for '=' signs, commas, and terminating NULL */ 378*26947304SEvan Yan len += (strlen(name) + strlen(value) + 2); 379*26947304SEvan Yan } 380*26947304SEvan Yan 381*26947304SEvan Yan /* Allocate the resulting options string */ 382*26947304SEvan Yan if ((options = (char *)calloc(len, sizeof (char))) == NULL) { 383*26947304SEvan Yan log_err("Failed to allocate private options string.\n"); 384*26947304SEvan Yan nvlist_free(nvl); 385*26947304SEvan Yan return; 386*26947304SEvan Yan } 387*26947304SEvan Yan 388*26947304SEvan Yan /* Copy name/value pairs into the options string */ 389*26947304SEvan Yan first_flag = B_TRUE; 390*26947304SEvan Yan for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) { 391*26947304SEvan Yan 392*26947304SEvan Yan name = nvpair_name(nvp); 393*26947304SEvan Yan 394*26947304SEvan Yan /* Skip the command, and anything not a string */ 395*26947304SEvan Yan if ((strcmp(name, "cmd") == 0) || 396*26947304SEvan Yan (nvpair_type(nvp) != DATA_TYPE_STRING)) 397*26947304SEvan Yan continue; 398*26947304SEvan Yan 399*26947304SEvan Yan if (!first_flag) 400*26947304SEvan Yan (void) strlcat(options, ",", len); 401*26947304SEvan Yan 402*26947304SEvan Yan (void) strlcat(options, name, len); 403*26947304SEvan Yan 404*26947304SEvan Yan (void) nvpair_value_string(nvp, &value); 405*26947304SEvan Yan 406*26947304SEvan Yan if (strlen(value) > 0) { 407*26947304SEvan Yan (void) strlcat(options, "=", len); 408*26947304SEvan Yan (void) strlcat(options, value, len); 409*26947304SEvan Yan } 410*26947304SEvan Yan 411*26947304SEvan Yan first_flag = B_FALSE; 412*26947304SEvan Yan } 413*26947304SEvan Yan 414*26947304SEvan Yan /* The unpacked nvlist is no longer needed */ 415*26947304SEvan Yan nvlist_free(nvl); 416*26947304SEvan Yan 417*26947304SEvan Yan /* Save results */ 418*26947304SEvan Yan *optionsp = options; 419*26947304SEvan Yan } 420*26947304SEvan Yan 421*26947304SEvan Yan /* 422*26947304SEvan Yan * free_properties() 423*26947304SEvan Yan * 424*26947304SEvan Yan * Destroy a structure containing a packed nvlist of bus 425*26947304SEvan Yan * private properties. 426*26947304SEvan Yan */ 427*26947304SEvan Yan static void 428*26947304SEvan Yan free_properties(ddi_hp_property_t *prop) 429*26947304SEvan Yan { 430*26947304SEvan Yan if (prop) { 431*26947304SEvan Yan if (prop->nvlist_buf) 432*26947304SEvan Yan free(prop->nvlist_buf); 433*26947304SEvan Yan (void) memset(prop, 0, sizeof (ddi_hp_property_t)); 434*26947304SEvan Yan } 435*26947304SEvan Yan } 436