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 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * sun4v Fault Isolation Services Module 31 */ 32 33 #include <sys/modctl.h> 34 #include <sys/cmn_err.h> 35 #include <sys/machsystm.h> 36 #include <sys/processor.h> 37 #include <sys/mem.h> 38 #include <vm/page.h> 39 #include <sys/note.h> 40 #include <sys/ds.h> 41 #include <sys/fault_iso.h> 42 43 /* 44 * Debugging routines 45 */ 46 #ifdef DEBUG 47 uint_t fi_debug = 0x0; 48 #define FI_DBG if (fi_debug) cmn_err 49 #else /* DEBUG */ 50 #define FI_DBG _NOTE(CONSTCOND) if (0) cmn_err 51 #endif /* DEBUG */ 52 53 /* 54 * Domains Services interaction 55 */ 56 static ds_svc_hdl_t cpu_handle; 57 static ds_svc_hdl_t mem_handle; 58 59 static ds_ver_t fi_vers[] = { { 1, 0 } }; 60 #define FI_NVERS (sizeof (fi_vers) / sizeof (fi_vers[0])) 61 62 static ds_capability_t cpu_cap = { 63 "fma-cpu-service", /* svc_id */ 64 fi_vers, /* vers */ 65 FI_NVERS /* nvers */ 66 }; 67 68 static ds_capability_t mem_cap = { 69 "fma-mem-service", /* svc_id */ 70 fi_vers, /* vers */ 71 FI_NVERS /* nvers */ 72 }; 73 74 static void fi_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl); 75 static void fi_unreg_handler(ds_cb_arg_t arg); 76 77 static void cpu_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen); 78 static void mem_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen); 79 80 static ds_clnt_ops_t cpu_ops = { 81 fi_reg_handler, /* ds_reg_cb */ 82 fi_unreg_handler, /* ds_unreg_cb */ 83 cpu_data_handler, /* ds_data_cb */ 84 &cpu_handle /* cb_arg */ 85 }; 86 87 static ds_clnt_ops_t mem_ops = { 88 fi_reg_handler, /* ds_reg_cb */ 89 fi_unreg_handler, /* ds_unreg_cb */ 90 mem_data_handler, /* ds_data_cb */ 91 &mem_handle /* cb_arg */ 92 }; 93 94 static int fi_init(void); 95 static void fi_fini(void); 96 97 static struct modlmisc modlmisc = { 98 &mod_miscops, 99 "sun4v Fault Isolation Services %I%" 100 }; 101 102 static struct modlinkage modlinkage = { 103 MODREV_1, 104 (void *)&modlmisc, 105 NULL 106 }; 107 108 int 109 _init(void) 110 { 111 int rv; 112 113 if ((rv = fi_init()) != 0) 114 return (rv); 115 116 if ((rv = mod_install(&modlinkage)) != 0) 117 fi_fini(); 118 119 return (rv); 120 } 121 122 int 123 _info(struct modinfo *modinfop) 124 { 125 return (mod_info(&modlinkage, modinfop)); 126 } 127 128 int fi_allow_unload; 129 130 int 131 _fini(void) 132 { 133 int status; 134 135 if (fi_allow_unload == 0) 136 return (EBUSY); 137 138 if ((status = mod_remove(&modlinkage)) == 0) 139 fi_fini(); 140 141 return (status); 142 } 143 144 static int 145 fi_init(void) 146 { 147 int rv; 148 149 /* register CPU service with domain services framework */ 150 rv = ds_cap_init(&cpu_cap, &cpu_ops); 151 if (rv != 0) { 152 FI_DBG(CE_CONT, "ds_cap_init failed: %d", rv); 153 return (rv); 154 } 155 156 /* register MEM servicewith domain services framework */ 157 rv = ds_cap_init(&mem_cap, &mem_ops); 158 if (rv != 0) { 159 FI_DBG(CE_CONT, "ds_cap_init failed: %d", rv); 160 (void) ds_cap_fini(&cpu_cap); 161 return (rv); 162 } 163 164 return (rv); 165 } 166 167 static void 168 fi_fini(void) 169 { 170 /* 171 * Stop incoming requests from Zeus 172 */ 173 (void) ds_cap_fini(&cpu_cap); 174 (void) ds_cap_fini(&mem_cap); 175 } 176 177 static void 178 cpu_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen) 179 { 180 _NOTE(ARGUNUSED(arg)) 181 182 fma_cpu_service_req_t *msg = buf; 183 fma_cpu_resp_t resp_msg; 184 int rv = 0; 185 int cpu_status; 186 int resp_back = 0; 187 188 /* 189 * If the buffer is the wrong size for CPU calls or is NULL then 190 * do not return any message. The call from the ldom mgr. will time out 191 * and the response will be NULL. 192 */ 193 if (msg == NULL || buflen != sizeof (fma_cpu_service_req_t)) { 194 return; 195 } 196 197 FI_DBG(CE_CONT, "req_num = %ld, msg_type = %d, cpu_id = %d\n", 198 msg->req_num, msg->msg_type, msg->cpu_id); 199 200 resp_msg.req_num = msg->req_num; 201 202 switch (msg->msg_type) { 203 case FMA_CPU_REQ_STATUS: 204 rv = p_online_internal(msg->cpu_id, P_STATUS, 205 &cpu_status); 206 if (rv == EINVAL) { 207 FI_DBG(CE_CONT, "Failed p_online call failed." 208 "Invalid CPU\n"); 209 resp_msg.result = FMA_CPU_RESP_FAILURE; 210 resp_msg.status = FMA_CPU_STAT_ILLEGAL; 211 resp_back = 1; 212 } 213 break; 214 case FMA_CPU_REQ_OFFLINE: 215 rv = p_online_internal(msg->cpu_id, P_FAULTED, 216 &cpu_status); 217 if (rv == EINVAL) { 218 FI_DBG(CE_CONT, "Failed p_online call failed." 219 "Invalid CPU\n"); 220 resp_msg.result = FMA_CPU_RESP_FAILURE; 221 resp_msg.status = FMA_CPU_STAT_ILLEGAL; 222 resp_back = 1; 223 } else if (rv == EBUSY) { 224 FI_DBG(CE_CONT, "Failed p_online call failed." 225 "Tried to offline while busy\n"); 226 resp_msg.result = FMA_CPU_RESP_FAILURE; 227 resp_msg.status = FMA_CPU_STAT_ONLINE; 228 resp_back = 1; 229 } 230 break; 231 case FMA_CPU_REQ_ONLINE: 232 rv = p_online_internal(msg->cpu_id, P_ONLINE, 233 &cpu_status); 234 if (rv == EINVAL) { 235 FI_DBG(CE_CONT, "Failed p_online call failed." 236 "Invalid CPU\n"); 237 resp_msg.result = FMA_CPU_RESP_FAILURE; 238 resp_msg.status = FMA_CPU_STAT_ILLEGAL; 239 resp_back = 1; 240 } else if (rv == ENOTSUP) { 241 FI_DBG(CE_CONT, "Failed p_online call failed." 242 "Online not supported for single CPU\n"); 243 resp_msg.result = FMA_CPU_RESP_FAILURE; 244 resp_msg.status = FMA_CPU_STAT_OFFLINE; 245 resp_back = 1; 246 } 247 break; 248 default: 249 /* 250 * If the msg_type was of unknown type simply return and 251 * have the ldom mgr. time out with a NULL response. 252 */ 253 return; 254 } 255 256 if (rv != 0) { 257 if (resp_back) { 258 if ((rv = ds_cap_send(cpu_handle, &resp_msg, 259 sizeof (resp_msg))) != 0) { 260 FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", 261 rv); 262 } 263 return; 264 } 265 ASSERT((rv == EINVAL) || ((rv == EBUSY) && 266 (msg->msg_type == FMA_CPU_REQ_OFFLINE)) || 267 ((rv == ENOTSUP) && (msg->msg_type == FMA_CPU_REQ_ONLINE))); 268 269 cmn_err(CE_WARN, "p_online_internal error not handled " 270 "rv = %d\n", rv); 271 } 272 273 resp_msg.req_num = msg->req_num; 274 resp_msg.result = FMA_CPU_RESP_OK; 275 276 switch (cpu_status) { 277 case P_OFFLINE: 278 case P_FAULTED: 279 case P_POWEROFF: 280 case P_SPARE: 281 resp_msg.status = FMA_CPU_STAT_OFFLINE; 282 break; 283 case P_ONLINE: 284 case P_NOINTR: 285 resp_msg.status = FMA_CPU_STAT_ONLINE; 286 break; 287 default: 288 resp_msg.status = FMA_CPU_STAT_ILLEGAL; 289 } 290 291 if ((rv = ds_cap_send(cpu_handle, &resp_msg, 292 sizeof (resp_msg))) != 0) { 293 FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", rv); 294 } 295 } 296 297 static void 298 mem_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen) 299 { 300 _NOTE(ARGUNUSED(arg)) 301 302 fma_mem_service_req_t *msg = buf; 303 fma_mem_resp_t resp_msg; 304 int rv = 0; 305 306 /* 307 * If the buffer is the wrong size for Mem calls or is NULL then 308 * do not return any message. The call from the ldom mgr. will time out 309 * and the response will be NULL. 310 */ 311 if (msg == NULL || buflen != sizeof (fma_mem_service_req_t)) { 312 return; 313 } 314 315 FI_DBG(CE_CONT, "req_num = %ld, msg_type = %d, memory addr = 0x%lx" 316 "memory length = 0x%lx\n", msg->req_num, msg->msg_type, 317 msg->real_addr, msg->length); 318 319 resp_msg.req_num = msg->req_num; 320 resp_msg.res_addr = msg->real_addr; 321 resp_msg.res_length = msg->length; 322 323 /* 324 * Information about return values for page calls can be referenced 325 * in usr/src/uts/common/vm/page_retire.c 326 */ 327 switch (msg->msg_type) { 328 case FMA_MEM_REQ_STATUS: 329 rv = page_retire_check(msg->real_addr, NULL); 330 switch (rv) { 331 /* Page is retired */ 332 case 0: 333 resp_msg.result = FMA_MEM_RESP_OK; 334 resp_msg.status = FMA_MEM_STAT_RETIRED; 335 break; 336 /* Page is pending. Send back failure and not retired */ 337 case EAGAIN: 338 resp_msg.result = FMA_MEM_RESP_FAILURE; 339 resp_msg.status = FMA_MEM_STAT_NOTRETIRED; 340 break; 341 /* Page is not retired. */ 342 case EIO: 343 resp_msg.result = FMA_MEM_RESP_OK; 344 resp_msg.status = FMA_MEM_STAT_NOTRETIRED; 345 break; 346 /* PA is not valid */ 347 case EINVAL: 348 resp_msg.result = FMA_MEM_RESP_FAILURE; 349 resp_msg.status = FMA_MEM_STAT_ILLEGAL; 350 break; 351 default: 352 ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) || 353 (rv == EINVAL)); 354 cmn_err(CE_WARN, "fault_iso: return value from " 355 "page_retire_check invalid: %d\n", rv); 356 } 357 break; 358 case FMA_MEM_REQ_RETIRE: 359 rv = page_retire(msg->real_addr, PR_FMA); 360 switch (rv) { 361 /* Page retired successfully */ 362 case 0: 363 resp_msg.result = FMA_MEM_RESP_OK; 364 resp_msg.status = FMA_MEM_STAT_RETIRED; 365 break; 366 /* Tried to retire and now Pending retirement */ 367 case EAGAIN: 368 resp_msg.result = FMA_MEM_RESP_FAILURE; 369 resp_msg.status = FMA_MEM_STAT_NOTRETIRED; 370 break; 371 /* Did not try to retire. Page already retired */ 372 case EIO: 373 resp_msg.result = FMA_MEM_RESP_FAILURE; 374 resp_msg.status = FMA_MEM_STAT_RETIRED; 375 break; 376 /* PA is not valid */ 377 case EINVAL: 378 resp_msg.result = FMA_MEM_RESP_FAILURE; 379 resp_msg.status = FMA_MEM_STAT_ILLEGAL; 380 break; 381 default: 382 ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) || 383 (rv == EINVAL)); 384 cmn_err(CE_WARN, "fault_iso: return value from " 385 "page_retire invalid: %d\n", rv); 386 } 387 break; 388 case FMA_MEM_REQ_RESURRECT: 389 rv = page_unretire(msg->real_addr); 390 switch (rv) { 391 /* Page succesfullly unretired */ 392 case 0: 393 resp_msg.result = FMA_MEM_RESP_OK; 394 resp_msg.status = FMA_MEM_STAT_NOTRETIRED; 395 break; 396 /* Page could not be locked. Still retired */ 397 case EAGAIN: 398 resp_msg.result = FMA_MEM_RESP_FAILURE; 399 resp_msg.status = FMA_MEM_STAT_RETIRED; 400 break; 401 /* Page was not retired already */ 402 case EIO: 403 resp_msg.result = FMA_MEM_RESP_FAILURE; 404 resp_msg.status = FMA_MEM_STAT_NOTRETIRED; 405 break; 406 /* PA is not valid */ 407 case EINVAL: 408 resp_msg.result = FMA_MEM_RESP_FAILURE; 409 resp_msg.status = FMA_MEM_STAT_ILLEGAL; 410 break; 411 default: 412 ASSERT((rv == 0) || (rv == EAGAIN) || (rv == EIO) || 413 (rv == EINVAL)); 414 cmn_err(CE_WARN, "fault_iso: return value from " 415 "page_unretire invalid: %d\n", rv); 416 } 417 break; 418 default: 419 /* 420 * If the msg_type was of unknown type simply return and 421 * have the ldom mgr. time out with a NULL response. 422 */ 423 return; 424 } 425 426 if ((rv = ds_cap_send(mem_handle, &resp_msg, sizeof (resp_msg))) != 0) { 427 FI_DBG(CE_CONT, "ds_cap_send failed (%d)\n", rv); 428 } 429 } 430 431 static void 432 fi_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl) 433 { 434 FI_DBG(CE_CONT, "fi_reg_handler: arg=0x%p, ver=%d.%d, hdl=0x%lx\n", 435 arg, ver->major, ver->minor, hdl); 436 437 if ((ds_svc_hdl_t *)arg == &cpu_handle) 438 cpu_handle = hdl; 439 if ((ds_svc_hdl_t *)arg == &mem_handle) 440 mem_handle = hdl; 441 } 442 443 static void 444 fi_unreg_handler(ds_cb_arg_t arg) 445 { 446 FI_DBG(CE_CONT, "fi_unreg_handler: arg=0x%p\n", arg); 447 448 if ((ds_svc_hdl_t *)arg == &cpu_handle) 449 cpu_handle = DS_INVALID_HDL; 450 if ((ds_svc_hdl_t *)arg == &mem_handle) 451 mem_handle = DS_INVALID_HDL; 452 } 453