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