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 if (nvl) 801 nvlist_free(nvl); 802 s_free(oldcpuids); 803 s_free(newcpuids); 804 return (rv); 805 } 806 807 static int 808 ap_rcm_cap_mem(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, uint_t flags, 809 rcm_info_t **rinfo, int cmd, long change) 810 { 811 int rv; 812 int pgsize; 813 long oldpages; 814 long newpages; 815 long currpages; 816 char buf[32]; 817 nvlist_t *nvl; 818 819 DBG("ap_rcm_cap_mem(%p)\n", (void *)a); 820 821 /* 822 * Get the current amount of configured memory. 823 */ 824 if ((pgsize = sysconf(_SC_PAGE_SIZE)) == -1 || 825 (currpages = sysconf(_SC_PHYS_PAGES)) == -1 || 826 nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) > 0) 827 return (RCM_FAILURE); 828 829 /* 830 * If this is a (delete) request, change represents 831 * the amount of capacity that will be deleted from the 832 * system. If this is an (add) notification, change 833 * represents the amount of capacity that has already 834 * been added to the system. 835 */ 836 if (cmd == CMD_RCM_CAP_DEL) { 837 oldpages = currpages; 838 newpages = currpages - change; 839 } else if (cmd == CMD_RCM_CAP_NOTIFY) { 840 newpages = currpages; 841 oldpages = rcm->cappages; 842 } else { 843 if (rcm->cappages == currpages) { 844 /* No real change in memory capacity */ 845 DBG("ap_rcm_cap_mem: no change in capacity.\n"); 846 nvlist_free(nvl); 847 return (RCM_SUCCESS); 848 } 849 850 oldpages = currpages - change; 851 newpages = currpages; 852 } 853 854 DBG("ap_rcm_cap_mem: Memory capacity, old = %ld, new = %ld\n", 855 oldpages, newpages); 856 857 if (nvlist_add_string(nvl, "state", "capacity") != 0 || 858 nvlist_add_int32(nvl, "page_size", pgsize) != 0 || 859 nvlist_add_int32(nvl, "old_pages", oldpages) != 0 || 860 nvlist_add_int32(nvl, "new_pages", newpages) != 0) { 861 nvlist_free(nvl); 862 return (RCM_FAILURE); 863 } 864 865 (void) snprintf(buf, sizeof (buf), "(%ld pages)", change); 866 ap_msg(a, MSG_ISSUE, cmd, buf); 867 868 if (cmd == CMD_RCM_CAP_DEL) { 869 rv = (*rcm->request_capacity_change)(hd, "SUNW_memory", 870 flags, nvl, rinfo); 871 } else { 872 rv = (*rcm->notify_capacity_change)(hd, "SUNW_memory", 873 flags & ~RCM_FORCE, nvl, rinfo); 874 } 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 } 930 if (npages && ((*rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 931 CMD_RCM_CAP_DEL, npages)) != RCM_SUCCESS)) { 932 return (CFGA_LIB_ERROR); 933 } 934 935 return (CFGA_OK); 936 } 937 938 static cfga_err_t 939 ap_rcm_add_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 940 int *rv, uint_t flags, rcm_info_t **rinfo) 941 { 942 int cm; 943 int ncpus; 944 long npages; 945 cap_info_t *capinfo, *prevcapinfo; 946 cfga_err_t rc; 947 948 DBG("ap_rcm_add_cap(%p)\n", (void *)a); 949 950 /* Get the new capacity info to figure out what has changed */ 951 if ((rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &capinfo)) != 952 CFGA_OK) 953 return (rc); 954 955 if (capinfo == NULL) { 956 DBG("no pertinent capacity info\n"); 957 return (CFGA_OK); 958 } 959 960 ncpus = npages = 0; 961 prevcapinfo = rcm->capinfo; 962 963 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 964 int i, j; 965 cfga_stat_t os, prevos; 966 int prevvalidity; 967 ap_target_t type; 968 969 /* 970 * See if the request target is a single 971 * (default) component 972 */ 973 i = cm == CM_DFLT ? 0 : cm; 974 975 os = capinfo[i].ostate; 976 if (prevcapinfo == NULL) { 977 prevos = CFGA_STAT_EMPTY; 978 prevvalidity = 1; 979 } else { 980 prevos = prevcapinfo[i].ostate; 981 prevvalidity = prevcapinfo[i].valid; 982 } 983 984 type = ap_cm_type(a, cm); 985 986 DBG("cm=%d valid=%d type=%d, prevos=%d os=%d\n", 987 cm, prevvalidity, type, prevos, os); 988 989 /* 990 * We are interested only in those components 991 * whose states have changed to configured as 992 * the result of the current cfgadm request. 993 */ 994 if (prevvalidity == 0 || os != CFGA_STAT_CONFIGURED) { 995 capinfo[i].valid = 0; 996 continue; 997 } else if (prevos != CFGA_STAT_CONFIGURED) { 998 /* 999 * The occupant state is configured, and 1000 * the previous occupant state was not. 1001 */ 1002 if ((type == AP_CPU) || (type == AP_CMP)) { 1003 for (j = 0; j < capinfo[i].ncap; j++) { 1004 rcm->cpuids[ncpus++] = 1005 capinfo[i].type.cpuid[j]; 1006 } 1007 } else if (type == AP_MEM) 1008 npages += capinfo[i].type.npages; 1009 } 1010 } 1011 free(capinfo); 1012 1013 if (ncpus && ((*rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 1014 CMD_RCM_CAP_ADD, ncpus)) != RCM_SUCCESS)) { 1015 return (CFGA_LIB_ERROR); 1016 } 1017 if (npages && ((*rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 1018 CMD_RCM_CAP_ADD, npages)) != RCM_SUCCESS)) { 1019 return (CFGA_LIB_ERROR); 1020 } 1021 1022 return (CFGA_OK); 1023 } 1024 1025 /* 1026 * ap_rcm_notify_cap: 1027 * 1028 * This routine handles the CMD_RCM_CAP_NOTIFY command. It 1029 * is called after a successful/failed DR unconfigure 1030 * operation. It filters out components that have changed 1031 * and passes this information on to ap_rcm_cap_{cpu,mem}. 1032 * 1033 * ap_rcm_cap_{cpu,mem} will still be called if all the 1034 * components have not changed and at least one {cpu,mem} 1035 * component was originally configured. 1036 */ 1037 static cfga_err_t 1038 ap_rcm_notify_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 1039 int *rv, uint_t flags, rcm_info_t **rinfo) 1040 { 1041 cfga_err_t rc; 1042 cap_info_t *capinfo; 1043 cap_info_t *prevcapinfo; 1044 int cm; 1045 long npages = 0; 1046 int ncpus = 0; 1047 int prev_mem = 0; /* # of prev. configured mem components */ 1048 int prev_cpus = 0; /* # of prev. configured CPUs */ 1049 1050 DBG("ap_rcm_notify_cap(%p)\n", (void *)a); 1051 1052 /* Get the new capacity info to figure out what has changed */ 1053 if ((rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &capinfo)) != 1054 CFGA_OK) 1055 return (rc); 1056 1057 if (capinfo == NULL) { 1058 DBG("no pertinent capacity info\n"); 1059 return (CFGA_OK); 1060 } 1061 1062 /* The original capacity info */ 1063 prevcapinfo = rcm->capinfo; 1064 1065 /* 1066 * Cycle through all components that we are operating 1067 * on. Record which components' occupant states have 1068 * changed. 1069 */ 1070 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 1071 int i; 1072 cfga_stat_t prevos, os; 1073 ap_target_t type; 1074 int prev_conf = 0; 1075 int now_conf = 0; 1076 1077 /* 1078 * See if the request target is a single 1079 * (default) component 1080 */ 1081 i = cm == CM_DFLT ? 0 : cm; 1082 1083 os = capinfo[i].ostate; 1084 1085 if (prevcapinfo == NULL) { 1086 prevos = CFGA_STAT_EMPTY; 1087 } else { 1088 prevos = prevcapinfo[i].ostate; 1089 if (prevcapinfo[i].valid == 0) { 1090 DBG("ap_rcm_notify_cap: skipping component " 1091 "due to prevvalidity == 0\n"); 1092 continue; 1093 } 1094 } 1095 1096 type = ap_cm_type(a, cm); 1097 1098 prev_conf = (prevos == CFGA_STAT_CONFIGURED); 1099 now_conf = (os == CFGA_STAT_CONFIGURED); 1100 1101 /* 1102 * Build up rcm->cpuids with the IDs of CPUs that 1103 * have been removed. Record the number of removed 1104 * CPUs and pages. 1105 */ 1106 if (type == AP_CPU || type == AP_CMP) { 1107 if (prev_conf) 1108 prev_cpus++; 1109 if (prev_conf && !now_conf) { 1110 int j; 1111 for (j = 0; j < capinfo[i].ncap; j++) { 1112 rcm->cpuids[ncpus++] = 1113 capinfo[i].type.cpuid[j]; 1114 } 1115 } 1116 } else if (type == AP_MEM) { 1117 if (prev_conf) 1118 prev_mem++; 1119 if (prev_conf && !now_conf) 1120 npages += capinfo[i].type.npages; 1121 } 1122 } 1123 free(capinfo); 1124 1125 /* 1126 * If any CPU or memory components were operated on, 1127 * successfully or not, the rcm_notify_capacity_change() 1128 * routine must be called. 1129 */ 1130 1131 if (prev_cpus) { 1132 *rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 1133 CMD_RCM_CAP_NOTIFY, ncpus); 1134 1135 if (*rv != RCM_SUCCESS) 1136 return (CFGA_LIB_ERROR); 1137 } 1138 1139 if (prev_mem) { 1140 *rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 1141 CMD_RCM_CAP_NOTIFY, npages); 1142 1143 if (*rv != RCM_SUCCESS) 1144 return (CFGA_LIB_ERROR); 1145 } 1146 1147 return (CFGA_OK); 1148 } 1149 1150 cfga_err_t 1151 ap_rcm_ctl(apd_t *a, int cmd) 1152 { 1153 int i; 1154 int rv; 1155 int noop; 1156 int ncpus; 1157 int cm; 1158 uint_t flags; 1159 char *rsrc; 1160 char **rlist; 1161 rcmd_t *rcm; 1162 rcm_info_t *rinfo; 1163 rcm_handle_t *hd; 1164 cfga_err_t rc; 1165 cpuid_t *growcpuids; 1166 1167 DBG("ap_rcm_ctl(%p)\n", (void *)a); 1168 1169 if ((rcm = (rcmd_t *)a->rcm) == NULL) { 1170 ap_msg(a, MSG_SKIP, cmd, a->target); 1171 return (CFGA_OK); 1172 } 1173 1174 hd = rcm->hd; 1175 rv = RCM_SUCCESS; 1176 rc = CFGA_OK; 1177 if (ap_getopt(a, OPT_FORCE)) 1178 flags = RCM_FORCE; 1179 else 1180 flags = 0; 1181 rinfo = NULL; 1182 rlist = NULL; 1183 rsrc = NULL; 1184 noop = 0; 1185 1186 switch (cmd) { 1187 case CMD_RCM_CAP_DEL: 1188 if (rcm->capinfo == NULL) 1189 noop++; 1190 else 1191 rc = ap_rcm_request_cap(a, rcm, hd, &rv, flags, &rinfo); 1192 break; 1193 case CMD_RCM_CAP_ADD: 1194 rc = ap_rcm_add_cap(a, rcm, hd, &rv, flags, &rinfo); 1195 break; 1196 case CMD_RCM_CAP_NOTIFY: 1197 rc = ap_rcm_notify_cap(a, rcm, hd, &rv, flags, &rinfo); 1198 break; 1199 case CMD_RCM_ONLINE: 1200 /* Refresh changed component states */ 1201 if ((rc = ap_stat(a, 1)) != CFGA_OK) { 1202 noop++; 1203 break; 1204 } 1205 1206 if (a->tgt == AP_BOARD) { 1207 rcm->firstcm = 0; 1208 rcm->lastcm = a->ncm - 1; 1209 1210 /* Check if we need to grow our cpuids list */ 1211 for (ncpus = 0, cm = rcm->firstcm; cm <= rcm->lastcm; 1212 cm++) { 1213 ap_target_t type = ap_cm_type(a, cm); 1214 if ((type == AP_CPU) || (type == AP_CMP)) 1215 ncpus += ap_cm_ncap(a, cm); 1216 } 1217 1218 if (rcm->ncpus < ncpus) { 1219 if ((growcpuids = 1220 (cpuid_t *)realloc(rcm->cpuids, 1221 (ncpus * sizeof (cpuid_t)))) == NULL) { 1222 ap_err(a, ERR_NOMEM); 1223 return (CFGA_LIB_ERROR); 1224 } 1225 rcm->ncpus = ncpus; 1226 rcm->cpuids = growcpuids; 1227 } 1228 1229 } else { 1230 rcm->firstcm = CM_DFLT; 1231 rcm->lastcm = CM_DFLT; 1232 } 1233 1234 /*FALLTHROUGH*/ 1235 1236 case CMD_RCM_OFFLINE: 1237 case CMD_RCM_REMOVE: { 1238 uint_t nrsrc; 1239 1240 if (cmd == CMD_RCM_REMOVE) { 1241 /* 1242 * An unconfigure has just taken place, so 1243 * refresh the changed component states. 1244 */ 1245 if ((rc = ap_stat(a, 1)) != CFGA_OK) { 1246 noop++; 1247 break; 1248 } 1249 } 1250 1251 /* Check if this is an empty board, i.e. no components */ 1252 if (a->ncm == 0) { 1253 noop++; 1254 break; 1255 } 1256 1257 if ((rlist = rcm->rlist) == NULL) { 1258 rc = ap_rcm_rlist(a, rcm->firstcm, rcm->lastcm, &rlist, 1259 cmd); 1260 if ((rc == CFGA_OK) && (rlist != NULL) && 1261 (rlist[0] != NULL)) { 1262 rcm->rlist = rlist; 1263 } else { 1264 /* Do not pass up empty resource list to RCM */ 1265 noop++; 1266 break; 1267 } 1268 } 1269 for (nrsrc = 0; rlist[nrsrc] != NULL; nrsrc++) 1270 ap_msg(a, MSG_ISSUE, cmd, rlist[nrsrc]); 1271 if (cmd == CMD_RCM_OFFLINE) 1272 rv = (*rcm->request_offline_list)(hd, rlist, flags, 1273 &rinfo); 1274 else if (cmd == CMD_RCM_ONLINE) 1275 rv = (*rcm->notify_online_list)(hd, rlist, 1276 flags & ~RCM_FORCE, &rinfo); 1277 else 1278 rv = (*rcm->notify_remove_list)(hd, rlist, 1279 flags & ~RCM_FORCE, &rinfo); 1280 break; 1281 } 1282 case CMD_RCM_SUSPEND: { 1283 timespec_t t; 1284 t.tv_sec = (time_t)0; 1285 t.tv_nsec = (long)0; 1286 rsrc = OS; 1287 ap_msg(a, MSG_ISSUE, cmd, rsrc); 1288 rv = (*rcm->request_suspend)(hd, rsrc, flags, &t, &rinfo); 1289 break; 1290 } 1291 case CMD_RCM_RESUME: 1292 rsrc = OS; 1293 ap_msg(a, MSG_ISSUE, cmd, rsrc); 1294 rv = (*rcm->notify_resume)(hd, rsrc, 0, &rinfo); 1295 break; 1296 default: 1297 ap_err(a, ERR_CMD_INVAL, cmd); 1298 return (CFGA_INVAL); 1299 } 1300 1301 if (rv != RCM_SUCCESS) { 1302 rcm->rinfo = rinfo; 1303 rcm->infot = NULL; 1304 ap_err(a, ERR_RCM_CMD, cmd); 1305 (*rcm->free_info)(rinfo); 1306 if (rc == CFGA_OK) 1307 rc = CFGA_LIB_ERROR; /* make sure error is set */ 1308 } 1309 if ((rc == CFGA_OK) && (noop == 0)) { 1310 if (rlist) 1311 for (i = 0; rlist[i]; i++) 1312 ap_msg(a, MSG_DONE, cmd, rlist[i]); 1313 else if (rsrc) 1314 ap_msg(a, MSG_DONE, cmd, rsrc); 1315 else 1316 ap_msg(a, MSG_DONE, cmd, a->target); 1317 } 1318 1319 return (rc); 1320 } 1321 1322 /* 1323 * ap_rcm_info 1324 * 1325 * Takes an ap_id and a character pointer, and formats 1326 * the rcm_info_t data in the form of a table to the given character pointer. 1327 * Code duplicated from the scsi plugin. 1328 * Note: This function will go away when a generic librcm callback is 1329 * implemented to format RCM messages for plugins. 1330 */ 1331 int 1332 ap_rcm_info(apd_t *a, char **msg) 1333 { 1334 rcmd_t *rcm; 1335 rcm_info_t *rinfo; 1336 int i; 1337 size_t w; 1338 size_t width = 0; 1339 size_t w_rsrc = 0; 1340 size_t w_info = 0; 1341 size_t msg_size = 0; 1342 uint_t tuples = 0; 1343 rcm_info_tuple_t *tuple = NULL; 1344 char *rsrc; 1345 char *info; 1346 char *newmsg; 1347 static char format[RCM_MAX_FORMAT]; 1348 const char *infostr; 1349 1350 1351 DBG("ap_rcm_info(%p)\n", (void *)a); 1352 1353 /* Protect against invalid arguments */ 1354 if ((a == NULL) || ((rcm = (rcmd_t *)a->rcm) == NULL) || 1355 ((rinfo = rcm->rinfo) == NULL) || (msg == NULL)) { 1356 return (-1); 1357 } 1358 1359 /* Set localized table header strings */ 1360 rsrc = dgettext(TEXT_DOMAIN, "Resource"); 1361 info = dgettext(TEXT_DOMAIN, "Information"); 1362 1363 /* A first pass, to size up the RCM information */ 1364 while (tuple = (*rcm->info_next)(rinfo, tuple)) { 1365 if ((infostr = (*rcm->info_info)(tuple)) != NULL) { 1366 tuples++; 1367 if ((w = strlen((*rcm->info_rsrc)(tuple))) > w_rsrc) 1368 w_rsrc = w; 1369 if ((w = strlen(infostr)) > w_info) 1370 w_info = w; 1371 } 1372 } 1373 1374 /* If nothing was sized up above, stop early */ 1375 if (tuples == 0) 1376 return (0); 1377 1378 /* Adjust column widths for column headings */ 1379 if ((w = strlen(rsrc)) > w_rsrc) 1380 w_rsrc = w; 1381 else if ((w_rsrc - w) % 2) 1382 w_rsrc++; 1383 if ((w = strlen(info)) > w_info) 1384 w_info = w; 1385 else if ((w_info - w) % 2) 1386 w_info++; 1387 1388 /* 1389 * Compute the total line width of each line, 1390 * accounting for intercolumn spacing. 1391 */ 1392 width = w_info + w_rsrc + 4; 1393 1394 /* Allocate space for the table */ 1395 msg_size = (2 + tuples) * (width + 1) + 2; 1396 if (*msg == NULL) { 1397 /* zero fill for the strcat() call below */ 1398 *msg = calloc(msg_size, sizeof (char)); 1399 if (*msg == NULL) 1400 return (-1); 1401 } else { 1402 newmsg = realloc(*msg, strlen(*msg) + msg_size); 1403 if (newmsg == NULL) 1404 return (-1); 1405 else 1406 *msg = newmsg; 1407 } 1408 1409 /* Place a table header into the string */ 1410 1411 /* The resource header */ 1412 (void) strcat(*msg, "\n"); 1413 w = strlen(rsrc); 1414 for (i = 0; i < ((w_rsrc - w) / 2); i++) 1415 (void) strcat(*msg, " "); 1416 (void) strcat(*msg, rsrc); 1417 for (i = 0; i < ((w_rsrc - w) / 2); i++) 1418 (void) strcat(*msg, " "); 1419 1420 /* The information header */ 1421 (void) strcat(*msg, " "); 1422 w = strlen(info); 1423 for (i = 0; i < ((w_info - w) / 2); i++) 1424 (void) strcat(*msg, " "); 1425 (void) strcat(*msg, info); 1426 for (i = 0; i < ((w_info - w) / 2); i++) 1427 (void) strcat(*msg, " "); 1428 1429 /* Underline the headers */ 1430 (void) strcat(*msg, "\n"); 1431 for (i = 0; i < w_rsrc; i++) 1432 (void) strcat(*msg, "-"); 1433 (void) strcat(*msg, " "); 1434 for (i = 0; i < w_info; i++) 1435 (void) strcat(*msg, "-"); 1436 1437 /* Construct the format string */ 1438 (void) snprintf(format, RCM_MAX_FORMAT, "%%-%ds %%-%ds", 1439 (int)w_rsrc, (int)w_info); 1440 1441 /* Add the tuples to the table string */ 1442 tuple = NULL; 1443 while ((tuple = (*rcm->info_next)(rinfo, tuple)) != NULL) { 1444 if ((infostr = (*rcm->info_info)(tuple)) != NULL) { 1445 (void) strcat(*msg, "\n"); 1446 (void) sprintf(&((*msg)[strlen(*msg)]), format, 1447 (*rcm->info_rsrc)(tuple), infostr); 1448 } 1449 } 1450 1451 DBG("ap_rcm_info(%p) success\n", (void *)a); 1452 return (0); 1453 } 1454