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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 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 #include <ctype.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <stdarg.h> 33 #include <string.h> 34 #include <unistd.h> 35 #include <macros.h> 36 #include <errno.h> 37 #include <kstat.h> 38 #include <sys/kmem.h> 39 #include <dlfcn.h> 40 #include <libdevinfo.h> 41 #include <librcm.h> 42 #include <libintl.h> 43 #define CFGA_PLUGIN_LIB 44 #include <config_admin.h> 45 #include <sys/sbd_ioctl.h> 46 #include "ap.h" 47 48 typedef int32_t cpuid_t; 49 50 typedef struct { 51 int valid; 52 cfga_stat_t ostate; 53 int ncap; 54 union { 55 long npages; 56 cpuid_t cpuid[SBD_MAX_CORES_PER_CMP]; 57 } type; 58 } cap_info_t; 59 60 typedef struct { 61 int firstcm; /* first component to operate on */ 62 int lastcm; /* last component to operate on */ 63 void *lib; 64 char **rlist; 65 cap_info_t *capinfo; 66 int ncpus; /* # of CPUs in cpuids list */ 67 cpuid_t *cpuids; /* List of cpuids */ 68 int capcpus; /* # of CPUs - tracking capacity */ 69 int cappages; /* # of memory pages - tracking capacity */ 70 rcm_handle_t *hd; 71 rcm_info_t *rinfo; 72 rcm_info_tuple_t *infot; 73 int (*alloc_handle)(char *, uint_t, void *, rcm_handle_t **); 74 void (*free_handle)(rcm_handle_t *); 75 int (*get_info)(rcm_handle_t *, char *, uint_t, rcm_info_t **); 76 void (*free_info)(rcm_info_t *); 77 rcm_info_tuple_t *(*info_next)(rcm_info_t *, rcm_info_tuple_t *); 78 int (*info_state)(rcm_info_tuple_t *); 79 pid_t (*info_pid)(rcm_info_tuple_t *); 80 const char *(*info_error)(rcm_info_tuple_t *); 81 const char *(*info_info)(rcm_info_tuple_t *); 82 const char *(*info_rsrc)(rcm_info_tuple_t *); 83 int (*request_offline_list)(rcm_handle_t *, char **, uint_t, 84 rcm_info_t **); 85 int (*notify_online_list)(rcm_handle_t *, char **, uint_t, 86 rcm_info_t **); 87 int (*request_suspend)(rcm_handle_t *, char *, uint_t, timespec_t *, 88 rcm_info_t **); 89 int (*notify_resume)(rcm_handle_t *, char *, uint_t, rcm_info_t **); 90 int (*notify_remove_list)(rcm_handle_t *, char **, uint_t, 91 rcm_info_t **); 92 int (*request_capacity_change)(rcm_handle_t *, char *, uint_t, 93 nvlist_t *, rcm_info_t **); 94 int (*notify_capacity_change)(rcm_handle_t *, char *, uint_t, 95 nvlist_t *, rcm_info_t **); 96 } rcmd_t; 97 98 static char * 99 ap_rcm_ops[] = { 100 "rcm_alloc_handle", 101 "rcm_free_handle", 102 "rcm_get_info", 103 "rcm_free_info", 104 "rcm_info_next", 105 "rcm_info_state", 106 "rcm_info_pid", 107 "rcm_info_error", 108 "rcm_info_info", 109 "rcm_info_rsrc", 110 "rcm_request_offline_list", 111 "rcm_notify_online_list", 112 "rcm_request_suspend", 113 "rcm_notify_resume", 114 "rcm_notify_remove_list", 115 "rcm_request_capacity_change", 116 "rcm_notify_capacity_change", 117 NULL 118 }; 119 120 #define ALLOC_HANDLE 0 121 #define FREE_HANDLE 1 122 #define GET_INFO 2 123 #define FREE_INFO 3 124 #define INFO_TUPLE_NEXT 4 125 #define INFO_TUPLE_STATE 5 126 #define INFO_TUPLE_ID 6 127 #define INFO_TUPLE_ERROR 7 128 #define INFO_TUPLE_INFO 8 129 #define INFO_TUPLE_RSRC 9 130 #define REQUEST_OFFLINE 10 131 #define NOTIFY_ONLINE 11 132 #define REQUEST_SUSPEND 12 133 #define NOTIFY_RESUME 13 134 #define NOTIFY_REMOVE 14 135 #define REQUEST_CAP_CHANGE 15 136 #define NOTIFY_CAP_CHANGE 16 137 138 /* 139 * There is no consumer for SUNW_OS. This is defined here 140 * for generic OS quiescence. 141 */ 142 #define OS "SUNW_OS" /* XXX */ 143 144 /* Max width of an RCM formatted message line */ 145 #define RCM_MAX_FORMAT 80 146 147 #ifdef __sparcv9 148 #define RCMLIB "/usr/lib/sparcv9/librcm.so"; 149 #else 150 #define RCMLIB "/usr/lib/librcm.so"; 151 #endif 152 153 static cfga_err_t 154 ap_capinfo(apd_t *a, int firstcm, int lastcm, cap_info_t **capinfo) 155 { 156 int cm; 157 int ncm; 158 void *cap; 159 int *ncap; 160 cfga_stat_t *os; 161 cap_info_t *cinfo, *cp; 162 163 DBG("ap_capinfo(%p)\n", (void *)a); 164 165 if (capinfo == NULL) { 166 ap_err(a, ERR_PLUGIN, "null capinfo"); 167 return (CFGA_LIB_ERROR); 168 } 169 170 /* 171 * Assume there are components with valid capacity 172 * information and allocate space for them. If there 173 * are none at the end, free the allocated space. 174 */ 175 ncm = lastcm - firstcm + 1; 176 177 cinfo = (cap_info_t *)calloc(ncm, sizeof (cap_info_t)); 178 if (cinfo == NULL) { 179 ap_err(a, ERR_NOMEM); 180 return (CFGA_LIB_ERROR); 181 } 182 183 *capinfo = NULL; 184 ncm = 0; 185 for (cp = cinfo, cm = firstcm; cm <= lastcm; cm++, cp++) { 186 os = &cp->ostate; 187 ncap = &cp->ncap; 188 189 switch (ap_cm_type(a, cm)) { 190 case AP_CPU: 191 case AP_CMP: 192 cap = (void *)(cp->type.cpuid); 193 break; 194 case AP_MEM: 195 cap = (void *)&(cp->type.npages); 196 break; 197 default: 198 continue; 199 } 200 /* 201 * Remember which components have valid 202 * capacity information. 203 */ 204 if (ap_cm_capacity(a, cm, cap, ncap, os)) { 205 cp->valid = 1; 206 ncm++; 207 } 208 } 209 210 if (ncm == 0) 211 free(cinfo); 212 else 213 *capinfo = cinfo; 214 215 return (CFGA_OK); 216 } 217 218 static int 219 getsyscpuids(int *ncpuids, cpuid_t **cpuids) 220 { 221 int ncpu; 222 int maxncpu; 223 kstat_t *ksp; 224 kstat_ctl_t *kc = NULL; 225 cpuid_t *cp; 226 227 DBG("getsyscpuids\n"); 228 229 if ((maxncpu = sysconf(_SC_NPROCESSORS_MAX)) == -1 || 230 (kc = kstat_open()) == NULL || 231 (cp = (cpuid_t *)calloc(maxncpu, sizeof (cpuid_t))) == NULL) { 232 /* if calloc failed, clean up kstats */ 233 if (kc != NULL) { 234 (void) kstat_close(kc); 235 } 236 return (-1); 237 } 238 239 DBG("syscpuids: "); 240 for (ncpu = 0, ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) { 241 if (strcmp(ksp->ks_module, "cpu_info") == 0) { 242 cp[ncpu++] = ksp->ks_instance; 243 DBG("%d ", ksp->ks_instance); 244 } 245 } 246 DBG("\n"); 247 248 (void) kstat_close(kc); 249 *cpuids = cp; 250 *ncpuids = ncpu; 251 return (0); 252 } 253 254 cfga_err_t 255 ap_rcm_init(apd_t *a) 256 { 257 int i; 258 char *err; 259 char *rcmlib; 260 void *sym; 261 void *lib; 262 char **op; 263 rcmd_t *rcm; 264 cfga_err_t rc; 265 struct stat buf; 266 267 DBG("ap_rcm_init(%p)\n", (void *)a); 268 269 /* 270 * If the initial command is status, or the RCM feature is not 271 * available, or the RCM interface has already been initialized, 272 * just return. 273 */ 274 275 if ((a->statonly != 0) || (a->norcm != 0) || 276 ((rcm = (rcmd_t *)a->rcm) != NULL)) { 277 return (CFGA_OK); 278 } 279 280 rcmlib = RCMLIB; 281 rc = CFGA_LIB_ERROR; 282 283 DBG("Looking for %s\n", rcmlib); 284 /* 285 * If the library is not present, there is nothing more 286 * to do. The RCM offline/suspend steps become no-ops 287 * in that case. 288 */ 289 if (stat(rcmlib, &buf) == -1) { 290 if (errno == ENOENT) { 291 a->norcm++; 292 ap_msg(a, MSG_NORCM); 293 return (CFGA_OK); 294 } else { 295 ap_err(a, ERR_STAT, rcmlib); 296 return (rc); 297 } 298 } 299 DBG("%s found\n", rcmlib); 300 301 if ((a->rcm = calloc(1, sizeof (rcmd_t))) == NULL) { 302 ap_err(a, ERR_NOMEM); 303 return (rc); 304 } 305 306 rcm = (rcmd_t *)a->rcm; 307 308 if ((lib = dlopen(rcmlib, RTLD_NOW)) == NULL) { 309 if ((err = dlerror()) != NULL) 310 err = strdup(err); 311 ap_err(a, ERR_LIB_OPEN, rcmlib, err); 312 if (err != NULL) 313 free(err); 314 return (rc); 315 } 316 317 rcm->lib = lib; 318 319 for (i = 0, op = ap_rcm_ops; *op != NULL; op++, i++) { 320 if ((sym = dlsym(lib, *op)) == NULL) { 321 ap_err(a, ERR_LIB_SYM, rcmlib, *op); 322 return (rc); 323 } 324 switch (i) { 325 case ALLOC_HANDLE: 326 rcm->alloc_handle = (int(*) 327 (char *, uint_t, void *, rcm_handle_t **))sym; 328 break; 329 case FREE_HANDLE: 330 rcm->free_handle = (void (*)(rcm_handle_t *))sym; 331 break; 332 case GET_INFO: 333 rcm->get_info = (int (*) 334 (rcm_handle_t *, char *, uint_t, 335 rcm_info_t **))sym; 336 break; 337 case FREE_INFO: 338 rcm->free_info = (void (*)(rcm_info_t *))sym; 339 break; 340 case INFO_TUPLE_NEXT: 341 rcm->info_next = (rcm_info_tuple_t *(*) 342 (rcm_info_t *, rcm_info_tuple_t *))sym; 343 break; 344 case INFO_TUPLE_STATE: 345 rcm->info_state = (int (*)(rcm_info_tuple_t *))sym; 346 break; 347 case INFO_TUPLE_ID: 348 rcm->info_pid = (pid_t (*)(rcm_info_tuple_t *))sym; 349 break; 350 case INFO_TUPLE_ERROR: 351 rcm->info_error = (const char *(*) 352 (rcm_info_tuple_t *))sym; 353 break; 354 case INFO_TUPLE_INFO: 355 rcm->info_info = (const char *(*) 356 (rcm_info_tuple_t *))sym; 357 break; 358 case INFO_TUPLE_RSRC: 359 rcm->info_rsrc = (const char *(*) 360 (rcm_info_tuple_t *))sym; 361 break; 362 case REQUEST_OFFLINE: 363 rcm->request_offline_list = (int (*) 364 (rcm_handle_t *, char **, uint_t, 365 rcm_info_t **))sym; 366 break; 367 case NOTIFY_ONLINE: 368 rcm->notify_online_list = (int (*) 369 (rcm_handle_t *, char **, uint_t, 370 rcm_info_t **))sym; 371 break; 372 case REQUEST_SUSPEND: 373 rcm->request_suspend = (int (*) 374 (rcm_handle_t *, char *, uint_t, 375 timespec_t *, rcm_info_t **))sym; 376 break; 377 case NOTIFY_RESUME: 378 rcm->notify_resume = (int (*) 379 (rcm_handle_t *, char *, uint_t, 380 rcm_info_t **))sym; 381 break; 382 case NOTIFY_REMOVE: 383 rcm->notify_remove_list = (int (*) 384 (rcm_handle_t *, char **, uint_t, 385 rcm_info_t **))sym; 386 break; 387 case REQUEST_CAP_CHANGE: 388 rcm->request_capacity_change = (int (*) 389 (rcm_handle_t *, char *, uint_t, 390 nvlist_t *, rcm_info_t **))sym; 391 break; 392 case NOTIFY_CAP_CHANGE: 393 rcm->notify_capacity_change = (int (*) 394 (rcm_handle_t *, char *, uint_t, 395 nvlist_t *, rcm_info_t **))sym; 396 break; 397 default: 398 break; 399 } 400 } 401 402 if (rcm->alloc_handle == NULL || 403 (*rcm->alloc_handle)(NULL, RCM_NOPID, NULL, &rcm->hd) 404 != RCM_SUCCESS) { 405 ap_err(a, ERR_RCM_HANDLE); 406 return (CFGA_LIB_ERROR); 407 } 408 409 /* 410 * Offlining/onlining a board means offlining/onlining 411 * all components on the board. When operating on a 412 * single component no component sequence number is 413 * needed since the default is the current (target) 414 * component. 415 */ 416 if (a->tgt == AP_BOARD) { 417 rcm->firstcm = 0; 418 rcm->lastcm = a->ncm - 1; 419 } else { 420 rcm->firstcm = CM_DFLT; 421 rcm->lastcm = CM_DFLT; 422 } 423 424 if (rcm->cpuids == NULL) { 425 int cm; 426 int ncpu; 427 428 /* 429 * Allocate space for the cpu capacity change info. 430 * Not every cpu may be relevant to the capacity 431 * request, but allocating for the maximum makes 432 * it easier, and the space is insignifcant. 433 */ 434 for (ncpu = 0, cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 435 436 ap_target_t type = ap_cm_type(a, cm); 437 438 if ((type == AP_CPU) || (type == AP_CMP)) { 439 ncpu += ap_cm_ncap(a, cm); 440 } 441 } 442 443 rcm->ncpus = ncpu; 444 if ((rcm->cpuids = (cpuid_t *)calloc(ncpu, sizeof (cpuid_t))) 445 == NULL) { 446 ap_err(a, ERR_NOMEM); 447 return (CFGA_LIB_ERROR); 448 } 449 } 450 451 /* 452 * Remember initial capacity information. 453 * This information is based on the initial 454 * state of the ap_id, i.e. before any 455 * state change change operations were 456 * executed. We will later get the 457 * current capacity information in order 458 * to figure out exactly what has changed 459 * as the result of the executed command 460 * sequence. 461 */ 462 rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &rcm->capinfo); 463 464 rcm->capcpus = sysconf(_SC_NPROCESSORS_CONF); 465 rcm->cappages = sysconf(_SC_PHYS_PAGES); 466 467 return (rc); 468 } 469 470 void 471 ap_rcm_fini(apd_t *a) 472 { 473 rcmd_t *rcm; 474 char **rp; 475 476 DBG("ap_rcm_fini(%p)\n", (void *)a); 477 478 if ((rcm = (rcmd_t *)a->rcm) == NULL) 479 return; 480 481 if (rcm->hd) 482 (*rcm->free_handle)(rcm->hd); 483 484 (void) dlclose(rcm->lib); 485 486 /* 487 * Free all the names in the resource list, followed 488 * by the resource list itself. 489 */ 490 if (rcm->rlist) 491 for (rp = rcm->rlist; *rp; rp++) 492 s_free(*rp); 493 s_free(rcm->rlist); 494 s_free(rcm->cpuids); 495 s_free(rcm->capinfo); 496 s_free(a->rcm); 497 } 498 499 static cfga_err_t 500 ap_rcm_rlist(apd_t *a, int firstcm, int lastcm, char ***rlist, int cmd) 501 { 502 int n; 503 int cm; 504 int ncap; 505 char *path; 506 char *cpuname; 507 char **rp; 508 509 DBG("ap_rcm_rlist(%p)\n", (void *)a); 510 511 /* 512 * Allocate space for the maximum number of components 513 * that can be affected by this operation. 514 */ 515 for (ncap = 0, cm = firstcm; cm <= lastcm; cm++) { 516 ncap += ap_cm_ncap(a, cm); 517 } 518 519 DBG("ncap=%d\n", ncap); 520 521 if ((rp = (char **)calloc(ncap + 1, sizeof (char *))) == NULL) { 522 ap_err(a, ERR_NOMEM); 523 return (CFGA_LIB_ERROR); 524 } 525 526 n = 12; /* SUNW_cpu/cpuCCC */ 527 /* <--- 12 ---> */ 528 cpuname = "SUNW_cpu/cpuCCC"; 529 /* 530 * Set the RCM resource name for each component: 531 * 532 * io: <device-path> 533 * cpu: SUNW_cpu/cpu<cpuid> 534 * 535 */ 536 for (ncap = 0, cm = firstcm; cm <= lastcm; cm++) { 537 switch (ap_cm_type(a, cm)) { 538 case AP_CPU: 539 case AP_CMP: { 540 int i; 541 int len; 542 cap_info_t cap; 543 cfga_stat_t os; 544 cpuid_t *cpuid; 545 int *nc; 546 cap_info_t *prevcap; 547 rcmd_t *rcm; 548 int allow_op; 549 int capindex; 550 551 cpuid = cap.type.cpuid; 552 nc = &cap.ncap; 553 554 /* 555 * See if the request target is a single 556 * (default) component 557 */ 558 capindex = (cm == CM_DFLT) ? 0 : cm; 559 560 /* Get the previous capacity info */ 561 rcm = (rcmd_t *)a->rcm; 562 prevcap = rcm->capinfo; 563 564 if (!ap_cm_capacity(a, cm, cpuid, nc, &os)) { 565 break; 566 } 567 568 len = (strlen(cpuname) - n) + 1; 569 570 /* 571 * For CMD_RCM_OFFLINE and REMOVE, add the CPU to the 572 * list if it is currently configured. For 573 * CMD_RCM_ONLINE, do so only if the state has changed 574 * to CFGA_STAT_CONFIGURED. 575 */ 576 allow_op = 0; 577 if ((cmd == CMD_RCM_OFFLINE) || 578 (cmd == CMD_RCM_REMOVE)) { 579 if (os == CFGA_STAT_CONFIGURED) 580 allow_op = 1; 581 } else { 582 if ((os == CFGA_STAT_CONFIGURED) && 583 ((prevcap == NULL) || 584 (prevcap[capindex].ostate != os))) 585 allow_op = 1; 586 } 587 588 if (allow_op) { 589 for (i = 0; i < *nc; i++) { 590 if ((path = strdup(cpuname)) == NULL) { 591 ap_err(a, ERR_NOMEM); 592 return (CFGA_LIB_ERROR); 593 } 594 (void) snprintf(&path[n], len, "%d", 595 cpuid[i]); 596 597 DBG("rp[%d]=%s\n", ncap, path); 598 rp[ncap++] = path; 599 } 600 } 601 break; 602 } 603 case AP_IO: 604 if ((path = ap_cm_devpath(a, cm)) != NULL) { 605 DBG("rp[%d]=%s\n", ncap, path); 606 rp[ncap++] = path; 607 } 608 break; 609 case AP_MEM: 610 /* 611 * Nothing to do for AP_MEM since only capacity 612 * change notifications apply to SUNW_memory 613 */ 614 default: 615 break; 616 } 617 } 618 619 rp[ncap] = NULL; 620 if (rlist) 621 *rlist = rp; 622 return (CFGA_OK); 623 } 624 625 /* 626 * Returns 1 if the cpu ID 'cpuid' is in the list of CPU IDs 627 * 'list' of length 'length'. Returns 0 otherwise. 628 */ 629 static int 630 is_cpu_in_list(cpuid_t cpuid, cpuid_t *list, int length) 631 { 632 int i; 633 634 DBG("is_cpu_in_list\n"); 635 636 if (list == NULL) 637 return (0); 638 639 for (i = 0; i < length; i++) { 640 if (list[i] == cpuid) 641 return (1); 642 } 643 return (0); 644 } 645 646 static int 647 ap_rcm_cap_cpu(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, uint_t flags, 648 rcm_info_t **rinfo, int cmd, int change) 649 { 650 int i; 651 int rv = RCM_FAILURE; 652 int ncpuids; 653 int oldncpuids; 654 int newncpuids; 655 char buf[32]; 656 const char *fmt; 657 size_t size; 658 nvlist_t *nvl = NULL; 659 cpuid_t *cpuids = NULL; 660 cpuid_t *oldcpuids = NULL; 661 cpuid_t *newcpuids = NULL; 662 663 DBG("ap_rcm_cap_cpu(%p)\n", (void *)a); 664 665 /* 666 * Get the current number of configured cpus. 667 */ 668 if (getsyscpuids(&ncpuids, &cpuids) == -1) 669 return (rv); 670 else if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { 671 free(cpuids); 672 goto done; 673 } 674 675 if (change == 1) 676 fmt = "(%d cpu)"; 677 else 678 fmt = "(%d cpus)"; 679 680 size = sizeof (cpuid_t); 681 682 if (cmd == CMD_RCM_CAP_DEL) { 683 /* 684 * A delete request. rcm->cpuids represents the 685 * cpus that will be unconfigured. The current 686 * set of cpus, before the unconfigure operation, 687 * are the old CPUs. The new CPUs are those 688 * that would remain. 689 */ 690 oldncpuids = ncpuids; 691 oldcpuids = cpuids; 692 693 /* 694 * Fill newcpuids with the CPU IDs in the cpuids array, 695 * but not in rcm->cpuids. 696 */ 697 newcpuids = (cpuid_t *)calloc(ncpuids, size); 698 if (newcpuids == NULL) 699 goto done; 700 701 newncpuids = 0; 702 for (i = 0; i < ncpuids; i++) { 703 if (!is_cpu_in_list(cpuids[i], rcm->cpuids, change)) 704 newcpuids[newncpuids++] = cpuids[i]; 705 } 706 } else if (cmd == CMD_RCM_CAP_NOTIFY) { 707 /* 708 * An unconfigure capacity change notification. This 709 * notification is sent after a DR unconfigure, whether 710 * or not the DR was successful. rcm->cpuids represents 711 * the CPUs that have been unconfigured. 712 */ 713 714 /* New CPU IDs are the CPUs configured right now. */ 715 newncpuids = ncpuids; 716 newcpuids = cpuids; 717 718 /* 719 * Old CPU IDs are the CPUs configured right now 720 * in addition to those that have been unconfigured. 721 * We build the old CPU ID list by concatenating 722 * cpuids and rcm->cpuids. 723 */ 724 oldcpuids = (cpuid_t *)calloc(ncpuids + change, size); 725 if (oldcpuids == NULL) 726 goto done; 727 728 oldncpuids = 0; 729 for (i = 0; i < ncpuids; i++) { 730 if (!is_cpu_in_list(cpuids[i], rcm->cpuids, change)) 731 oldcpuids[oldncpuids++] = cpuids[i]; 732 } 733 for (i = 0; i < change; i++) 734 oldcpuids[oldncpuids++] = rcm->cpuids[i]; 735 } else { 736 DBG("ap_rcm_cap_cpu: CPU capacity, old = %d, new = %d \n", 737 rcm->capcpus, ncpuids); 738 if (rcm->capcpus == ncpuids) { 739 /* No real change in CPU capacity */ 740 rv = RCM_SUCCESS; 741 goto done; 742 } 743 744 /* 745 * An add notification. rcm->cpuids represents the 746 * cpus that have been configured. The current 747 * set of cpus, after the configure operation, 748 * are the new CPU IDs. 749 */ 750 newncpuids = ncpuids; 751 newcpuids = cpuids; 752 753 /* 754 * Fill oldcpuids with the CPU IDs in the cpuids array, 755 * but not in rcm->cpuids. 756 */ 757 oldcpuids = (cpuid_t *)calloc(ncpuids, size); 758 if (oldcpuids == NULL) 759 goto done; 760 761 oldncpuids = 0; 762 for (i = 0; i < ncpuids; i++) { 763 if (!is_cpu_in_list(cpuids[i], rcm->cpuids, change)) 764 oldcpuids[oldncpuids++] = cpuids[i]; 765 } 766 } 767 768 DBG("oldcpuids: "); 769 for (i = 0; i < oldncpuids; i++) 770 DBG("%d ", oldcpuids[i]); 771 DBG("\n"); 772 DBG("change : "); 773 for (i = 0; i < change; i++) 774 DBG("%d ", rcm->cpuids[i]); 775 DBG("\n"); 776 DBG("newcpuids: "); 777 for (i = 0; i < newncpuids; i++) 778 DBG("%d ", newcpuids[i]); 779 DBG("\n"); 780 781 if (nvlist_add_string(nvl, "state", "capacity") != 0 || 782 nvlist_add_int32(nvl, "old_total", oldncpuids) != 0 || 783 nvlist_add_int32(nvl, "new_total", newncpuids) != 0 || 784 nvlist_add_int32_array(nvl, "old_cpu_list", oldcpuids, 785 oldncpuids) != 0 || 786 nvlist_add_int32_array(nvl, "new_cpu_list", newcpuids, 787 newncpuids) != 0) 788 goto done; 789 790 snprintf(buf, sizeof (buf), fmt, change); 791 ap_msg(a, MSG_ISSUE, cmd, buf); 792 793 if (cmd == CMD_RCM_CAP_DEL) 794 rv = (*rcm->request_capacity_change)(hd, "SUNW_cpu", 795 flags, nvl, rinfo); 796 else 797 rv = (*rcm->notify_capacity_change)(hd, "SUNW_cpu", 798 flags & ~RCM_FORCE, nvl, rinfo); 799 800 done: 801 if (nvl) 802 nvlist_free(nvl); 803 s_free(oldcpuids); 804 s_free(newcpuids); 805 return (rv); 806 } 807 808 static int 809 ap_rcm_cap_mem(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, uint_t flags, 810 rcm_info_t **rinfo, int cmd, long change) 811 { 812 int rv; 813 int pgsize; 814 long oldpages; 815 long newpages; 816 long currpages; 817 char buf[32]; 818 nvlist_t *nvl; 819 820 DBG("ap_rcm_cap_mem(%p)\n", (void *)a); 821 822 /* 823 * Get the current amount of configured memory. 824 */ 825 if ((pgsize = sysconf(_SC_PAGE_SIZE)) == -1 || 826 (currpages = sysconf(_SC_PHYS_PAGES)) == -1 || 827 nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) > 0) 828 return (RCM_FAILURE); 829 830 /* 831 * If this is a (delete) request, change represents 832 * the amount of capacity that will be deleted from the 833 * system. If this is an (add) notification, change 834 * represents the amount of capacity that has already 835 * been added to the system. 836 */ 837 if (cmd == CMD_RCM_CAP_DEL) { 838 oldpages = currpages; 839 newpages = currpages - change; 840 } else if (cmd == CMD_RCM_CAP_NOTIFY) { 841 newpages = currpages; 842 oldpages = rcm->cappages; 843 } else { 844 if (rcm->cappages == currpages) { 845 /* No real change in memory capacity */ 846 DBG("ap_rcm_cap_mem: no change in capacity.\n"); 847 nvlist_free(nvl); 848 return (RCM_SUCCESS); 849 } 850 851 oldpages = currpages - change; 852 newpages = currpages; 853 } 854 855 DBG("ap_rcm_cap_mem: Memory capacity, old = %ld, new = %ld\n", 856 oldpages, newpages); 857 858 if (nvlist_add_string(nvl, "state", "capacity") != 0 || 859 nvlist_add_int32(nvl, "page_size", pgsize) != 0 || 860 nvlist_add_int32(nvl, "old_pages", oldpages) != 0 || 861 nvlist_add_int32(nvl, "new_pages", newpages) != 0) { 862 nvlist_free(nvl); 863 return (RCM_FAILURE); 864 } 865 866 (void) snprintf(buf, sizeof (buf), "(%ld pages)", change); 867 ap_msg(a, MSG_ISSUE, cmd, buf); 868 869 if (cmd == CMD_RCM_CAP_DEL) 870 rv = (*rcm->request_capacity_change)(hd, "SUNW_memory", 871 flags, nvl, rinfo); 872 else 873 rv = (*rcm->notify_capacity_change)(hd, "SUNW_memory", 874 flags & ~RCM_FORCE, nvl, rinfo); 875 876 nvlist_free(nvl); 877 878 return (rv); 879 } 880 881 static cfga_err_t 882 ap_rcm_request_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 883 int *rv, uint_t flags, rcm_info_t **rinfo) 884 { 885 int cm; 886 int ncpus; 887 long npages; 888 cap_info_t *capinfo; 889 ap_target_t type; 890 891 DBG("ap_rcm_request_cap(%p)\n", (void *)a); 892 893 if ((capinfo = rcm->capinfo) == NULL) { 894 ap_err(a, ERR_PLUGIN, "null capinfo"); 895 return (CFGA_LIB_ERROR); 896 } 897 898 ncpus = npages = 0; 899 900 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 901 int i, j; 902 903 /* 904 * See if the request target is a single 905 * (default) component 906 */ 907 i = (cm == CM_DFLT) ? 0 : cm; 908 909 /* 910 * We are interested only in those components 911 * in the configured state since they represent 912 * available capacity. 913 */ 914 type = ap_cm_type(a, cm); 915 if (capinfo[i].valid == 0 || 916 capinfo[i].ostate != CFGA_STAT_CONFIGURED) 917 continue; 918 else if ((type == AP_CPU) || (type == AP_CMP)) { 919 for (j = 0; j < capinfo[i].ncap; j++) { 920 rcm->cpuids[ncpus++] = capinfo[i].type.cpuid[j]; 921 } 922 } else if (type == AP_MEM) 923 npages += capinfo[i].type.npages; 924 } 925 926 if (ncpus && ((*rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 927 CMD_RCM_CAP_DEL, ncpus)) != RCM_SUCCESS)) 928 return (CFGA_LIB_ERROR); 929 if (npages && ((*rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 930 CMD_RCM_CAP_DEL, npages)) != RCM_SUCCESS)) 931 return (CFGA_LIB_ERROR); 932 933 return (CFGA_OK); 934 } 935 936 static cfga_err_t 937 ap_rcm_add_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 938 int *rv, uint_t flags, rcm_info_t **rinfo) 939 { 940 int cm; 941 int ncpus; 942 long npages; 943 cap_info_t *capinfo, *prevcapinfo; 944 cfga_err_t rc; 945 946 DBG("ap_rcm_add_cap(%p)\n", (void *)a); 947 948 /* Get the new capacity info to figure out what has changed */ 949 if ((rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &capinfo)) 950 != CFGA_OK) 951 return (rc); 952 953 if (capinfo == NULL) { 954 DBG("no pertinent capacity info\n"); 955 return (CFGA_OK); 956 } 957 958 ncpus = npages = 0; 959 prevcapinfo = rcm->capinfo; 960 961 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 962 int i, j; 963 cfga_stat_t os, prevos; 964 int prevvalidity; 965 ap_target_t type; 966 967 /* 968 * See if the request target is a single 969 * (default) component 970 */ 971 i = cm == CM_DFLT ? 0 : cm; 972 973 os = capinfo[i].ostate; 974 if (prevcapinfo == NULL) { 975 prevos = CFGA_STAT_EMPTY; 976 prevvalidity = 1; 977 } else { 978 prevos = prevcapinfo[i].ostate; 979 prevvalidity = prevcapinfo[i].valid; 980 } 981 982 type = ap_cm_type(a, cm); 983 984 DBG("cm=%d valid=%d type=%d, prevos=%d os=%d\n", 985 cm, prevvalidity, type, prevos, os); 986 987 /* 988 * We are interested only in those components 989 * whose states have changed to configured as 990 * the result of the current cfgadm request. 991 */ 992 if (prevvalidity == 0 || os != CFGA_STAT_CONFIGURED) { 993 capinfo[i].valid = 0; 994 continue; 995 } else if (prevos != CFGA_STAT_CONFIGURED) { 996 /* 997 * The occupant state is configured, and 998 * the previous occupant state was not. 999 */ 1000 if ((type == AP_CPU) || (type == AP_CMP)) { 1001 for (j = 0; j < capinfo[i].ncap; j++) { 1002 rcm->cpuids[ncpus++] = 1003 capinfo[i].type.cpuid[j]; 1004 } 1005 } else if (type == AP_MEM) 1006 npages += capinfo[i].type.npages; 1007 } 1008 } 1009 free(capinfo); 1010 1011 if (ncpus && ((*rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 1012 CMD_RCM_CAP_ADD, ncpus)) != RCM_SUCCESS)) 1013 return (CFGA_LIB_ERROR); 1014 if (npages && ((*rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 1015 CMD_RCM_CAP_ADD, npages)) != RCM_SUCCESS)) 1016 return (CFGA_LIB_ERROR); 1017 1018 return (CFGA_OK); 1019 } 1020 1021 /* 1022 * ap_rcm_notify_cap: 1023 * 1024 * This routine handles the CMD_RCM_CAP_NOTIFY command. It 1025 * is called after a successful/failed DR unconfigure 1026 * operation. It filters out components that have changed 1027 * and passes this information on to ap_rcm_cap_{cpu,mem}. 1028 * 1029 * ap_rcm_cap_{cpu,mem} will still be called if all the 1030 * components have not changed and at least one {cpu,mem} 1031 * component was originally configured. 1032 */ 1033 static cfga_err_t 1034 ap_rcm_notify_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 1035 int *rv, uint_t flags, rcm_info_t **rinfo) 1036 { 1037 cfga_err_t rc; 1038 cap_info_t *capinfo; 1039 cap_info_t *prevcapinfo; 1040 int cm; 1041 long npages = 0; 1042 int ncpus = 0; 1043 int prev_mem = 0; /* # of prev. configured mem components */ 1044 int prev_cpus = 0; /* # of prev. configured CPUs */ 1045 1046 DBG("ap_rcm_notify_cap(%p)\n", (void *)a); 1047 1048 /* Get the new capacity info to figure out what has changed */ 1049 if ((rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &capinfo)) 1050 != CFGA_OK) 1051 return (rc); 1052 1053 if (capinfo == NULL) { 1054 DBG("no pertinent capacity info\n"); 1055 return (CFGA_OK); 1056 } 1057 1058 /* The original capacity info */ 1059 prevcapinfo = rcm->capinfo; 1060 1061 /* 1062 * Cycle through all components that we are operating 1063 * on. Record which components' occupant states have 1064 * changed. 1065 */ 1066 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 1067 int i; 1068 cfga_stat_t prevos, os; 1069 ap_target_t type; 1070 int prev_conf = 0; 1071 int now_conf = 0; 1072 1073 /* 1074 * See if the request target is a single 1075 * (default) component 1076 */ 1077 i = cm == CM_DFLT ? 0 : cm; 1078 1079 os = capinfo[i].ostate; 1080 1081 if (prevcapinfo == NULL) { 1082 prevos = CFGA_STAT_EMPTY; 1083 } else { 1084 prevos = prevcapinfo[i].ostate; 1085 if (prevcapinfo[i].valid == 0) { 1086 DBG("ap_rcm_notify_cap: skipping component " 1087 "due to prevvalidity == 0\n"); 1088 continue; 1089 } 1090 } 1091 1092 type = ap_cm_type(a, cm); 1093 1094 prev_conf = (prevos == CFGA_STAT_CONFIGURED); 1095 now_conf = (os == CFGA_STAT_CONFIGURED); 1096 1097 /* 1098 * Build up rcm->cpuids with the IDs of CPUs that 1099 * have been removed. Record the number of removed 1100 * CPUs and pages. 1101 */ 1102 if (type == AP_CPU || type == AP_CMP) { 1103 if (prev_conf) 1104 prev_cpus++; 1105 if (prev_conf && !now_conf) { 1106 int j; 1107 for (j = 0; j < capinfo[i].ncap; j++) { 1108 rcm->cpuids[ncpus++] = 1109 capinfo[i].type.cpuid[j]; 1110 } 1111 } 1112 } else if (type == AP_MEM) { 1113 if (prev_conf) 1114 prev_mem++; 1115 if (prev_conf && !now_conf) 1116 npages += capinfo[i].type.npages; 1117 } 1118 } 1119 free(capinfo); 1120 1121 /* 1122 * If any CPU or memory components were operated on, 1123 * successfully or not, the rcm_notify_capacity_change() 1124 * routine must be called. 1125 */ 1126 1127 if (prev_cpus) { 1128 *rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 1129 CMD_RCM_CAP_NOTIFY, ncpus); 1130 1131 if (*rv != RCM_SUCCESS) 1132 return (CFGA_LIB_ERROR); 1133 } 1134 1135 if (prev_mem) { 1136 *rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 1137 CMD_RCM_CAP_NOTIFY, npages); 1138 1139 if (*rv != RCM_SUCCESS) 1140 return (CFGA_LIB_ERROR); 1141 } 1142 1143 return (CFGA_OK); 1144 } 1145 1146 cfga_err_t 1147 ap_rcm_ctl(apd_t *a, int cmd) 1148 { 1149 int i; 1150 int rv; 1151 int noop; 1152 int ncpus; 1153 int cm; 1154 uint_t flags; 1155 char *rsrc; 1156 char **rlist; 1157 rcmd_t *rcm; 1158 rcm_info_t *rinfo; 1159 rcm_handle_t *hd; 1160 cfga_err_t rc; 1161 cpuid_t *growcpuids; 1162 1163 DBG("ap_rcm_ctl(%p)\n", (void *)a); 1164 1165 if ((rcm = (rcmd_t *)a->rcm) == NULL) { 1166 ap_msg(a, MSG_SKIP, cmd, a->target); 1167 return (CFGA_OK); 1168 } 1169 1170 hd = rcm->hd; 1171 rv = RCM_SUCCESS; 1172 rc = CFGA_OK; 1173 if (ap_getopt(a, OPT_FORCE)) 1174 flags = RCM_FORCE; 1175 else 1176 flags = 0; 1177 rinfo = NULL; 1178 rlist = NULL; 1179 rsrc = NULL; 1180 noop = 0; 1181 1182 switch (cmd) { 1183 case CMD_RCM_CAP_DEL: 1184 if (rcm->capinfo == NULL) 1185 noop++; 1186 else 1187 rc = ap_rcm_request_cap(a, rcm, hd, &rv, 1188 flags, &rinfo); 1189 break; 1190 case CMD_RCM_CAP_ADD: 1191 rc = ap_rcm_add_cap(a, rcm, hd, &rv, flags, &rinfo); 1192 break; 1193 case CMD_RCM_CAP_NOTIFY: 1194 rc = ap_rcm_notify_cap(a, rcm, hd, &rv, flags, &rinfo); 1195 break; 1196 case CMD_RCM_ONLINE: 1197 /* Refresh changed component states */ 1198 if ((rc = ap_stat(a, 1)) != CFGA_OK) { 1199 noop++; 1200 break; 1201 } 1202 1203 if (a->tgt == AP_BOARD) { 1204 rcm->firstcm = 0; 1205 rcm->lastcm = a->ncm - 1; 1206 1207 /* Check if we need to grow our cpuids list */ 1208 for (ncpus = 0, cm = rcm->firstcm; cm <= rcm->lastcm; 1209 cm++) { 1210 ap_target_t type = ap_cm_type(a, cm); 1211 if ((type == AP_CPU) || (type == AP_CMP)) 1212 ncpus += ap_cm_ncap(a, cm); 1213 } 1214 1215 if (rcm->ncpus < ncpus) { 1216 if ((growcpuids = 1217 (cpuid_t *)realloc(rcm->cpuids, 1218 (ncpus * sizeof (cpuid_t)))) == NULL) { 1219 ap_err(a, ERR_NOMEM); 1220 return (CFGA_LIB_ERROR); 1221 } 1222 rcm->ncpus = ncpus; 1223 rcm->cpuids = growcpuids; 1224 } 1225 1226 } else { 1227 rcm->firstcm = CM_DFLT; 1228 rcm->lastcm = CM_DFLT; 1229 } 1230 1231 /*FALLTHROUGH*/ 1232 1233 case CMD_RCM_OFFLINE: 1234 case CMD_RCM_REMOVE: { 1235 uint_t nrsrc; 1236 1237 if (cmd == CMD_RCM_REMOVE) { 1238 /* 1239 * An unconfigure has just taken place, so 1240 * refresh the changed component states. 1241 */ 1242 if ((rc = ap_stat(a, 1)) != CFGA_OK) { 1243 noop++; 1244 break; 1245 } 1246 } 1247 1248 /* Check if this is an empty board, i.e. no components */ 1249 if (a->ncm == 0) { 1250 noop++; 1251 break; 1252 } 1253 1254 if ((rlist = rcm->rlist) == NULL) { 1255 rc = ap_rcm_rlist(a, rcm->firstcm, rcm->lastcm, &rlist, 1256 cmd); 1257 if ((rc == CFGA_OK) && (rlist != NULL) && 1258 (rlist[0] != NULL)) { 1259 rcm->rlist = rlist; 1260 } else { 1261 /* Do not pass up empty resource list to RCM */ 1262 noop++; 1263 break; 1264 } 1265 } 1266 for (nrsrc = 0; rlist[nrsrc] != NULL; nrsrc++) 1267 ap_msg(a, MSG_ISSUE, cmd, rlist[nrsrc]); 1268 if (cmd == CMD_RCM_OFFLINE) 1269 rv = (*rcm->request_offline_list)(hd, rlist, flags, 1270 &rinfo); 1271 else if (cmd == CMD_RCM_ONLINE) 1272 rv = (*rcm->notify_online_list)(hd, rlist, 1273 flags & ~RCM_FORCE, &rinfo); 1274 else 1275 rv = (*rcm->notify_remove_list)(hd, rlist, 1276 flags & ~RCM_FORCE, &rinfo); 1277 break; 1278 } 1279 case CMD_RCM_SUSPEND: { 1280 timespec_t t; 1281 t.tv_sec = (time_t)0; 1282 t.tv_nsec = (long)0; 1283 rsrc = OS; 1284 ap_msg(a, MSG_ISSUE, cmd, rsrc); 1285 rv = (*rcm->request_suspend)(hd, rsrc, flags, &t, &rinfo); 1286 break; 1287 } 1288 case CMD_RCM_RESUME: 1289 rsrc = OS; 1290 ap_msg(a, MSG_ISSUE, cmd, rsrc); 1291 rv = (*rcm->notify_resume)(hd, rsrc, 0, &rinfo); 1292 break; 1293 default: 1294 ap_err(a, ERR_CMD_INVAL, cmd); 1295 return (CFGA_INVAL); 1296 } 1297 1298 if (rv != RCM_SUCCESS) { 1299 rcm->rinfo = rinfo; 1300 rcm->infot = NULL; 1301 ap_err(a, ERR_RCM_CMD, cmd); 1302 (*rcm->free_info)(rinfo); 1303 if (rc == CFGA_OK) 1304 rc = CFGA_LIB_ERROR; /* make sure error is set */ 1305 } 1306 if ((rc == CFGA_OK) && (noop == 0)) { 1307 if (rlist) 1308 for (i = 0; rlist[i]; i++) 1309 ap_msg(a, MSG_DONE, cmd, rlist[i]); 1310 else if (rsrc) 1311 ap_msg(a, MSG_DONE, cmd, rsrc); 1312 else 1313 ap_msg(a, MSG_DONE, cmd, a->target); 1314 } 1315 1316 return (rc); 1317 } 1318 1319 /* 1320 * ap_rcm_info 1321 * 1322 * Takes an ap_id and a character pointer, and formats 1323 * the rcm_info_t data in the form of a table to the given character pointer. 1324 * Code duplicated from the scsi plugin. 1325 * Note: This function will go away when a generic librcm callback is 1326 * implemented to format RCM messages for plugins. 1327 */ 1328 int 1329 ap_rcm_info(apd_t *a, char **msg) 1330 { 1331 rcmd_t *rcm; 1332 rcm_info_t *rinfo; 1333 int i; 1334 size_t w; 1335 size_t width = 0; 1336 size_t w_rsrc = 0; 1337 size_t w_info = 0; 1338 size_t msg_size = 0; 1339 uint_t tuples = 0; 1340 rcm_info_tuple_t *tuple = NULL; 1341 char *rsrc; 1342 char *info; 1343 char *newmsg; 1344 static char format[RCM_MAX_FORMAT]; 1345 const char *infostr; 1346 1347 1348 DBG("ap_rcm_info(%p)\n", (void *)a); 1349 1350 /* Protect against invalid arguments */ 1351 if ((a == NULL) || ((rcm = (rcmd_t *)a->rcm) == NULL) || 1352 ((rinfo = rcm->rinfo) == NULL) || (msg == NULL)) { 1353 return (-1); 1354 } 1355 1356 /* Set localized table header strings */ 1357 rsrc = dgettext(TEXT_DOMAIN, "Resource"); 1358 info = dgettext(TEXT_DOMAIN, "Information"); 1359 1360 /* A first pass, to size up the RCM information */ 1361 while (tuple = (*rcm->info_next)(rinfo, tuple)) { 1362 if ((infostr = (*rcm->info_info)(tuple)) != NULL) { 1363 tuples++; 1364 if ((w = strlen((*rcm->info_rsrc)(tuple))) > w_rsrc) 1365 w_rsrc = w; 1366 if ((w = strlen(infostr)) > w_info) 1367 w_info = w; 1368 } 1369 } 1370 1371 /* If nothing was sized up above, stop early */ 1372 if (tuples == 0) 1373 return (0); 1374 1375 /* Adjust column widths for column headings */ 1376 if ((w = strlen(rsrc)) > w_rsrc) 1377 w_rsrc = w; 1378 else if ((w_rsrc - w) % 2) 1379 w_rsrc++; 1380 if ((w = strlen(info)) > w_info) 1381 w_info = w; 1382 else if ((w_info - w) % 2) 1383 w_info++; 1384 1385 /* 1386 * Compute the total line width of each line, 1387 * accounting for intercolumn spacing. 1388 */ 1389 width = w_info + w_rsrc + 4; 1390 1391 /* Allocate space for the table */ 1392 msg_size = (2 + tuples) * (width + 1) + 2; 1393 if (*msg == NULL) { 1394 /* zero fill for the strcat() call below */ 1395 *msg = calloc(msg_size, sizeof (char)); 1396 if (*msg == NULL) 1397 return (-1); 1398 } else { 1399 newmsg = realloc(*msg, strlen(*msg) + msg_size); 1400 if (newmsg == NULL) 1401 return (-1); 1402 else 1403 *msg = newmsg; 1404 } 1405 1406 /* Place a table header into the string */ 1407 1408 /* The resource header */ 1409 (void) strcat(*msg, "\n"); 1410 w = strlen(rsrc); 1411 for (i = 0; i < ((w_rsrc - w) / 2); i++) 1412 (void) strcat(*msg, " "); 1413 (void) strcat(*msg, rsrc); 1414 for (i = 0; i < ((w_rsrc - w) / 2); i++) 1415 (void) strcat(*msg, " "); 1416 1417 /* The information header */ 1418 (void) strcat(*msg, " "); 1419 w = strlen(info); 1420 for (i = 0; i < ((w_info - w) / 2); i++) 1421 (void) strcat(*msg, " "); 1422 (void) strcat(*msg, info); 1423 for (i = 0; i < ((w_info - w) / 2); i++) 1424 (void) strcat(*msg, " "); 1425 1426 /* Underline the headers */ 1427 (void) strcat(*msg, "\n"); 1428 for (i = 0; i < w_rsrc; i++) 1429 (void) strcat(*msg, "-"); 1430 (void) strcat(*msg, " "); 1431 for (i = 0; i < w_info; i++) 1432 (void) strcat(*msg, "-"); 1433 1434 /* Construct the format string */ 1435 (void) snprintf(format, RCM_MAX_FORMAT, "%%-%ds %%-%ds", 1436 (int)w_rsrc, (int)w_info); 1437 1438 /* Add the tuples to the table string */ 1439 tuple = NULL; 1440 while ((tuple = (*rcm->info_next)(rinfo, tuple)) != NULL) { 1441 if ((infostr = (*rcm->info_info)(tuple)) != NULL) { 1442 (void) strcat(*msg, "\n"); 1443 (void) sprintf(&((*msg)[strlen(*msg)]), format, 1444 (*rcm->info_rsrc)(tuple), infostr); 1445 } 1446 } 1447 1448 DBG("ap_rcm_info(%p) success\n", (void *)a); 1449 return (0); 1450 } 1451