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 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <ctype.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <stdarg.h> 32 #include <string.h> 33 #include <unistd.h> 34 #include <macros.h> 35 #include <errno.h> 36 #include <kstat.h> 37 #include <sys/kmem.h> 38 #include <dlfcn.h> 39 #include <libdevinfo.h> 40 #include <librcm.h> 41 #include <libintl.h> 42 #define CFGA_PLUGIN_LIB 43 #include <config_admin.h> 44 #include <sys/sbd_ioctl.h> 45 #include "ap.h" 46 47 typedef int32_t cpuid_t; 48 49 typedef struct { 50 int valid; 51 cfga_stat_t ostate; 52 int ncap; 53 union { 54 long npages; 55 cpuid_t cpuid[SBD_MAX_CORES_PER_CMP]; 56 } type; 57 } cap_info_t; 58 59 typedef struct { 60 int firstcm; /* first component to operate on */ 61 int lastcm; /* last component to operate on */ 62 void *lib; 63 char **rlist; 64 cap_info_t *capinfo; 65 int ncpus; /* # of CPUs in cpuids list */ 66 cpuid_t *cpuids; /* List of cpuids */ 67 int capcpus; /* # of CPUs - tracking capacity */ 68 int cappages; /* # of memory pages - tracking capacity */ 69 rcm_handle_t *hd; 70 rcm_info_t *rinfo; 71 rcm_info_tuple_t *infot; 72 int (*alloc_handle)(char *, uint_t, void *, rcm_handle_t **); 73 void (*free_handle)(rcm_handle_t *); 74 int (*get_info)(rcm_handle_t *, char *, uint_t, rcm_info_t **); 75 void (*free_info)(rcm_info_t *); 76 rcm_info_tuple_t *(*info_next)(rcm_info_t *, rcm_info_tuple_t *); 77 int (*info_state)(rcm_info_tuple_t *); 78 pid_t (*info_pid)(rcm_info_tuple_t *); 79 const char *(*info_error)(rcm_info_tuple_t *); 80 const char *(*info_info)(rcm_info_tuple_t *); 81 const char *(*info_rsrc)(rcm_info_tuple_t *); 82 int (*request_offline_list)(rcm_handle_t *, char **, uint_t, 83 rcm_info_t **); 84 int (*notify_online_list)(rcm_handle_t *, char **, uint_t, 85 rcm_info_t **); 86 int (*request_suspend)(rcm_handle_t *, char *, uint_t, timespec_t *, 87 rcm_info_t **); 88 int (*notify_resume)(rcm_handle_t *, char *, uint_t, rcm_info_t **); 89 int (*notify_remove_list)(rcm_handle_t *, char **, uint_t, 90 rcm_info_t **); 91 int (*request_capacity_change)(rcm_handle_t *, char *, uint_t, 92 nvlist_t *, rcm_info_t **); 93 int (*notify_capacity_change)(rcm_handle_t *, char *, uint_t, 94 nvlist_t *, rcm_info_t **); 95 } rcmd_t; 96 97 static char * 98 ap_rcm_ops[] = { 99 "rcm_alloc_handle", 100 "rcm_free_handle", 101 "rcm_get_info", 102 "rcm_free_info", 103 "rcm_info_next", 104 "rcm_info_state", 105 "rcm_info_pid", 106 "rcm_info_error", 107 "rcm_info_info", 108 "rcm_info_rsrc", 109 "rcm_request_offline_list", 110 "rcm_notify_online_list", 111 "rcm_request_suspend", 112 "rcm_notify_resume", 113 "rcm_notify_remove_list", 114 "rcm_request_capacity_change", 115 "rcm_notify_capacity_change", 116 NULL 117 }; 118 119 #define ALLOC_HANDLE 0 120 #define FREE_HANDLE 1 121 #define GET_INFO 2 122 #define FREE_INFO 3 123 #define INFO_TUPLE_NEXT 4 124 #define INFO_TUPLE_STATE 5 125 #define INFO_TUPLE_ID 6 126 #define INFO_TUPLE_ERROR 7 127 #define INFO_TUPLE_INFO 8 128 #define INFO_TUPLE_RSRC 9 129 #define REQUEST_OFFLINE 10 130 #define NOTIFY_ONLINE 11 131 #define REQUEST_SUSPEND 12 132 #define NOTIFY_RESUME 13 133 #define NOTIFY_REMOVE 14 134 #define REQUEST_CAP_CHANGE 15 135 #define NOTIFY_CAP_CHANGE 16 136 137 /* 138 * There is no consumer for SUNW_OS. This is defined here 139 * for generic OS quiescence. 140 */ 141 #define OS "SUNW_OS" /* XXX */ 142 143 /* Max width of an RCM formatted message line */ 144 #define RCM_MAX_FORMAT 80 145 146 #ifdef __sparcv9 147 #define RCMLIB "/lib/sparcv9/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, 334 rcm_info_t **))sym; 335 break; 336 case FREE_INFO: 337 rcm->free_info = (void (*)(rcm_info_t *))sym; 338 break; 339 case INFO_TUPLE_NEXT: 340 rcm->info_next = (rcm_info_tuple_t *(*) 341 (rcm_info_t *, rcm_info_tuple_t *))sym; 342 break; 343 case INFO_TUPLE_STATE: 344 rcm->info_state = (int (*)(rcm_info_tuple_t *))sym; 345 break; 346 case INFO_TUPLE_ID: 347 rcm->info_pid = (pid_t (*)(rcm_info_tuple_t *))sym; 348 break; 349 case INFO_TUPLE_ERROR: 350 rcm->info_error = (const char *(*) 351 (rcm_info_tuple_t *))sym; 352 break; 353 case INFO_TUPLE_INFO: 354 rcm->info_info = (const char *(*) 355 (rcm_info_tuple_t *))sym; 356 break; 357 case INFO_TUPLE_RSRC: 358 rcm->info_rsrc = (const char *(*) 359 (rcm_info_tuple_t *))sym; 360 break; 361 case REQUEST_OFFLINE: 362 rcm->request_offline_list = (int (*) 363 (rcm_handle_t *, char **, uint_t, 364 rcm_info_t **))sym; 365 break; 366 case NOTIFY_ONLINE: 367 rcm->notify_online_list = (int (*) 368 (rcm_handle_t *, char **, uint_t, 369 rcm_info_t **))sym; 370 break; 371 case REQUEST_SUSPEND: 372 rcm->request_suspend = (int (*) 373 (rcm_handle_t *, char *, uint_t, 374 timespec_t *, rcm_info_t **))sym; 375 break; 376 case NOTIFY_RESUME: 377 rcm->notify_resume = (int (*) 378 (rcm_handle_t *, char *, uint_t, 379 rcm_info_t **))sym; 380 break; 381 case NOTIFY_REMOVE: 382 rcm->notify_remove_list = (int (*) 383 (rcm_handle_t *, char **, uint_t, 384 rcm_info_t **))sym; 385 break; 386 case REQUEST_CAP_CHANGE: 387 rcm->request_capacity_change = (int (*) 388 (rcm_handle_t *, char *, uint_t, 389 nvlist_t *, rcm_info_t **))sym; 390 break; 391 case NOTIFY_CAP_CHANGE: 392 rcm->notify_capacity_change = (int (*) 393 (rcm_handle_t *, char *, uint_t, 394 nvlist_t *, rcm_info_t **))sym; 395 break; 396 default: 397 break; 398 } 399 } 400 401 if (rcm->alloc_handle == NULL || 402 (*rcm->alloc_handle)(NULL, RCM_NOPID, NULL, &rcm->hd) 403 != RCM_SUCCESS) { 404 ap_err(a, ERR_RCM_HANDLE); 405 return (CFGA_LIB_ERROR); 406 } 407 408 /* 409 * Offlining/onlining a board means offlining/onlining 410 * all components on the board. When operating on a 411 * single component no component sequence number is 412 * needed since the default is the current (target) 413 * component. 414 */ 415 if (a->tgt == AP_BOARD) { 416 rcm->firstcm = 0; 417 rcm->lastcm = a->ncm - 1; 418 } else { 419 rcm->firstcm = CM_DFLT; 420 rcm->lastcm = CM_DFLT; 421 } 422 423 if (rcm->cpuids == NULL) { 424 int cm; 425 int ncpu; 426 427 /* 428 * Allocate space for the cpu capacity change info. 429 * Not every cpu may be relevant to the capacity 430 * request, but allocating for the maximum makes 431 * it easier, and the space is insignifcant. 432 */ 433 for (ncpu = 0, cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 434 435 ap_target_t type = ap_cm_type(a, cm); 436 437 if ((type == AP_CPU) || (type == AP_CMP)) { 438 ncpu += ap_cm_ncap(a, cm); 439 } 440 } 441 442 rcm->ncpus = ncpu; 443 if ((rcm->cpuids = (cpuid_t *)calloc(ncpu, sizeof (cpuid_t))) 444 == NULL) { 445 ap_err(a, ERR_NOMEM); 446 return (CFGA_LIB_ERROR); 447 } 448 } 449 450 /* 451 * Remember initial capacity information. 452 * This information is based on the initial 453 * state of the ap_id, i.e. before any 454 * state change change operations were 455 * executed. We will later get the 456 * current capacity information in order 457 * to figure out exactly what has changed 458 * as the result of the executed command 459 * sequence. 460 */ 461 rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &rcm->capinfo); 462 463 rcm->capcpus = sysconf(_SC_NPROCESSORS_CONF); 464 rcm->cappages = sysconf(_SC_PHYS_PAGES); 465 466 return (rc); 467 } 468 469 void 470 ap_rcm_fini(apd_t *a) 471 { 472 rcmd_t *rcm; 473 char **rp; 474 475 DBG("ap_rcm_fini(%p)\n", (void *)a); 476 477 if ((rcm = (rcmd_t *)a->rcm) == NULL) 478 return; 479 480 if (rcm->hd) 481 (*rcm->free_handle)(rcm->hd); 482 483 (void) dlclose(rcm->lib); 484 485 /* 486 * Free all the names in the resource list, followed 487 * by the resource list itself. 488 */ 489 if (rcm->rlist) 490 for (rp = rcm->rlist; *rp; rp++) 491 s_free(*rp); 492 s_free(rcm->rlist); 493 s_free(rcm->cpuids); 494 s_free(rcm->capinfo); 495 s_free(a->rcm); 496 } 497 498 static cfga_err_t 499 ap_rcm_rlist(apd_t *a, int firstcm, int lastcm, char ***rlist, int cmd) 500 { 501 int n; 502 int cm; 503 int ncap; 504 char *path; 505 char *cpuname; 506 char **rp; 507 508 DBG("ap_rcm_rlist(%p)\n", (void *)a); 509 510 /* 511 * Allocate space for the maximum number of components 512 * that can be affected by this operation. 513 */ 514 for (ncap = 0, cm = firstcm; cm <= lastcm; cm++) { 515 ncap += ap_cm_ncap(a, cm); 516 } 517 518 DBG("ncap=%d\n", ncap); 519 520 if ((rp = (char **)calloc(ncap + 1, sizeof (char *))) == NULL) { 521 ap_err(a, ERR_NOMEM); 522 return (CFGA_LIB_ERROR); 523 } 524 525 n = 12; /* SUNW_cpu/cpuCCC */ 526 /* <--- 12 ---> */ 527 cpuname = "SUNW_cpu/cpuCCC"; 528 /* 529 * Set the RCM resource name for each component: 530 * 531 * io: <device-path> 532 * cpu: SUNW_cpu/cpu<cpuid> 533 * 534 */ 535 for (ncap = 0, cm = firstcm; cm <= lastcm; cm++) { 536 switch (ap_cm_type(a, cm)) { 537 case AP_CPU: 538 case AP_CMP: { 539 int i; 540 int len; 541 cap_info_t cap; 542 cfga_stat_t os; 543 cpuid_t *cpuid; 544 int *nc; 545 cap_info_t *prevcap; 546 rcmd_t *rcm; 547 int allow_op; 548 int capindex; 549 550 cpuid = cap.type.cpuid; 551 nc = &cap.ncap; 552 553 /* 554 * See if the request target is a single 555 * (default) component 556 */ 557 capindex = (cm == CM_DFLT) ? 0 : cm; 558 559 /* Get the previous capacity info */ 560 rcm = (rcmd_t *)a->rcm; 561 prevcap = rcm->capinfo; 562 563 if (!ap_cm_capacity(a, cm, cpuid, nc, &os)) { 564 break; 565 } 566 567 len = (strlen(cpuname) - n) + 1; 568 569 /* 570 * For CMD_RCM_OFFLINE and REMOVE, add the CPU to the 571 * list if it is currently configured. For 572 * CMD_RCM_ONLINE, do so only if the state has changed 573 * to CFGA_STAT_CONFIGURED. 574 */ 575 allow_op = 0; 576 if ((cmd == CMD_RCM_OFFLINE) || 577 (cmd == CMD_RCM_REMOVE)) { 578 if (os == CFGA_STAT_CONFIGURED) 579 allow_op = 1; 580 } else { 581 if ((os == CFGA_STAT_CONFIGURED) && 582 ((prevcap == NULL) || 583 (prevcap[capindex].ostate != os))) 584 allow_op = 1; 585 } 586 587 if (allow_op) { 588 for (i = 0; i < *nc; i++) { 589 if ((path = strdup(cpuname)) == NULL) { 590 ap_err(a, ERR_NOMEM); 591 return (CFGA_LIB_ERROR); 592 } 593 (void) snprintf(&path[n], len, "%d", 594 cpuid[i]); 595 596 DBG("rp[%d]=%s\n", ncap, path); 597 rp[ncap++] = path; 598 } 599 } 600 break; 601 } 602 case AP_IO: 603 if ((path = ap_cm_devpath(a, cm)) != NULL) { 604 DBG("rp[%d]=%s\n", ncap, path); 605 rp[ncap++] = path; 606 } 607 break; 608 case AP_MEM: 609 /* 610 * Nothing to do for AP_MEM since only capacity 611 * change notifications apply to SUNW_memory 612 */ 613 default: 614 break; 615 } 616 } 617 618 rp[ncap] = NULL; 619 if (rlist) 620 *rlist = rp; 621 return (CFGA_OK); 622 } 623 624 /* 625 * Returns 1 if the cpu ID 'cpuid' is in the list of CPU IDs 626 * 'list' of length 'length'. Returns 0 otherwise. 627 */ 628 static int 629 is_cpu_in_list(cpuid_t cpuid, cpuid_t *list, int length) 630 { 631 int i; 632 633 DBG("is_cpu_in_list\n"); 634 635 if (list == NULL) 636 return (0); 637 638 for (i = 0; i < length; i++) { 639 if (list[i] == cpuid) 640 return (1); 641 } 642 return (0); 643 } 644 645 static int 646 ap_rcm_cap_cpu(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, uint_t flags, 647 rcm_info_t **rinfo, int cmd, int change) 648 { 649 int i; 650 int rv = RCM_FAILURE; 651 int ncpuids; 652 int oldncpuids; 653 int newncpuids; 654 char buf[32]; 655 const char *fmt; 656 size_t size; 657 nvlist_t *nvl = NULL; 658 cpuid_t *cpuids = NULL; 659 cpuid_t *oldcpuids = NULL; 660 cpuid_t *newcpuids = NULL; 661 662 DBG("ap_rcm_cap_cpu(%p)\n", (void *)a); 663 664 /* 665 * Get the current number of configured cpus. 666 */ 667 if (getsyscpuids(&ncpuids, &cpuids) == -1) 668 return (rv); 669 else if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { 670 free(cpuids); 671 goto done; 672 } 673 674 if (change == 1) 675 fmt = "(%d cpu)"; 676 else 677 fmt = "(%d cpus)"; 678 679 size = sizeof (cpuid_t); 680 681 if (cmd == CMD_RCM_CAP_DEL) { 682 /* 683 * A delete request. rcm->cpuids represents the 684 * cpus that will be unconfigured. The current 685 * set of cpus, before the unconfigure operation, 686 * are the old CPUs. The new CPUs are those 687 * that would remain. 688 */ 689 oldncpuids = ncpuids; 690 oldcpuids = cpuids; 691 692 /* 693 * Fill newcpuids with the CPU IDs in the cpuids array, 694 * but not in rcm->cpuids. 695 */ 696 newcpuids = (cpuid_t *)calloc(ncpuids, size); 697 if (newcpuids == NULL) 698 goto done; 699 700 newncpuids = 0; 701 for (i = 0; i < ncpuids; i++) { 702 if (!is_cpu_in_list(cpuids[i], rcm->cpuids, change)) 703 newcpuids[newncpuids++] = cpuids[i]; 704 } 705 } else if (cmd == CMD_RCM_CAP_NOTIFY) { 706 /* 707 * An unconfigure capacity change notification. This 708 * notification is sent after a DR unconfigure, whether 709 * or not the DR was successful. rcm->cpuids represents 710 * the CPUs that have been unconfigured. 711 */ 712 713 /* New CPU IDs are the CPUs configured right now. */ 714 newncpuids = ncpuids; 715 newcpuids = cpuids; 716 717 /* 718 * Old CPU IDs are the CPUs configured right now 719 * in addition to those that have been unconfigured. 720 * We build the old CPU ID list by concatenating 721 * cpuids and rcm->cpuids. 722 */ 723 oldcpuids = (cpuid_t *)calloc(ncpuids + change, size); 724 if (oldcpuids == NULL) 725 goto done; 726 727 oldncpuids = 0; 728 for (i = 0; i < ncpuids; i++) { 729 if (!is_cpu_in_list(cpuids[i], rcm->cpuids, change)) 730 oldcpuids[oldncpuids++] = cpuids[i]; 731 } 732 for (i = 0; i < change; i++) 733 oldcpuids[oldncpuids++] = rcm->cpuids[i]; 734 } else { 735 DBG("ap_rcm_cap_cpu: CPU capacity, old = %d, new = %d \n", 736 rcm->capcpus, ncpuids); 737 if (rcm->capcpus == ncpuids) { 738 /* No real change in CPU capacity */ 739 rv = RCM_SUCCESS; 740 goto done; 741 } 742 743 /* 744 * An add notification. rcm->cpuids represents the 745 * cpus that have been configured. The current 746 * set of cpus, after the configure operation, 747 * are the new CPU IDs. 748 */ 749 newncpuids = ncpuids; 750 newcpuids = cpuids; 751 752 /* 753 * Fill oldcpuids with the CPU IDs in the cpuids array, 754 * but not in rcm->cpuids. 755 */ 756 oldcpuids = (cpuid_t *)calloc(ncpuids, size); 757 if (oldcpuids == NULL) 758 goto done; 759 760 oldncpuids = 0; 761 for (i = 0; i < ncpuids; i++) { 762 if (!is_cpu_in_list(cpuids[i], rcm->cpuids, change)) 763 oldcpuids[oldncpuids++] = cpuids[i]; 764 } 765 } 766 767 DBG("oldcpuids: "); 768 for (i = 0; i < oldncpuids; i++) 769 DBG("%d ", oldcpuids[i]); 770 DBG("\n"); 771 DBG("change : "); 772 for (i = 0; i < change; i++) 773 DBG("%d ", rcm->cpuids[i]); 774 DBG("\n"); 775 DBG("newcpuids: "); 776 for (i = 0; i < newncpuids; i++) 777 DBG("%d ", newcpuids[i]); 778 DBG("\n"); 779 780 if (nvlist_add_string(nvl, "state", "capacity") != 0 || 781 nvlist_add_int32(nvl, "old_total", oldncpuids) != 0 || 782 nvlist_add_int32(nvl, "new_total", newncpuids) != 0 || 783 nvlist_add_int32_array(nvl, "old_cpu_list", oldcpuids, 784 oldncpuids) != 0 || 785 nvlist_add_int32_array(nvl, "new_cpu_list", newcpuids, 786 newncpuids) != 0) 787 goto done; 788 789 snprintf(buf, sizeof (buf), fmt, change); 790 ap_msg(a, MSG_ISSUE, cmd, buf); 791 792 if (cmd == CMD_RCM_CAP_DEL) 793 rv = (*rcm->request_capacity_change)(hd, "SUNW_cpu", 794 flags, nvl, rinfo); 795 else 796 rv = (*rcm->notify_capacity_change)(hd, "SUNW_cpu", 797 flags & ~RCM_FORCE, nvl, rinfo); 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 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 if (npages && ((*rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 929 CMD_RCM_CAP_DEL, npages)) != RCM_SUCCESS)) 930 return (CFGA_LIB_ERROR); 931 932 return (CFGA_OK); 933 } 934 935 static cfga_err_t 936 ap_rcm_add_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 937 int *rv, uint_t flags, rcm_info_t **rinfo) 938 { 939 int cm; 940 int ncpus; 941 long npages; 942 cap_info_t *capinfo, *prevcapinfo; 943 cfga_err_t rc; 944 945 DBG("ap_rcm_add_cap(%p)\n", (void *)a); 946 947 /* Get the new capacity info to figure out what has changed */ 948 if ((rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &capinfo)) 949 != CFGA_OK) 950 return (rc); 951 952 if (capinfo == NULL) { 953 DBG("no pertinent capacity info\n"); 954 return (CFGA_OK); 955 } 956 957 ncpus = npages = 0; 958 prevcapinfo = rcm->capinfo; 959 960 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 961 int i, j; 962 cfga_stat_t os, prevos; 963 int prevvalidity; 964 ap_target_t type; 965 966 /* 967 * See if the request target is a single 968 * (default) component 969 */ 970 i = cm == CM_DFLT ? 0 : cm; 971 972 os = capinfo[i].ostate; 973 if (prevcapinfo == NULL) { 974 prevos = CFGA_STAT_EMPTY; 975 prevvalidity = 1; 976 } else { 977 prevos = prevcapinfo[i].ostate; 978 prevvalidity = prevcapinfo[i].valid; 979 } 980 981 type = ap_cm_type(a, cm); 982 983 DBG("cm=%d valid=%d type=%d, prevos=%d os=%d\n", 984 cm, prevvalidity, type, prevos, os); 985 986 /* 987 * We are interested only in those components 988 * whose states have changed to configured as 989 * the result of the current cfgadm request. 990 */ 991 if (prevvalidity == 0 || os != CFGA_STAT_CONFIGURED) { 992 capinfo[i].valid = 0; 993 continue; 994 } else if (prevos != CFGA_STAT_CONFIGURED) { 995 /* 996 * The occupant state is configured, and 997 * the previous occupant state was not. 998 */ 999 if ((type == AP_CPU) || (type == AP_CMP)) { 1000 for (j = 0; j < capinfo[i].ncap; j++) { 1001 rcm->cpuids[ncpus++] = 1002 capinfo[i].type.cpuid[j]; 1003 } 1004 } else if (type == AP_MEM) 1005 npages += capinfo[i].type.npages; 1006 } 1007 } 1008 free(capinfo); 1009 1010 if (ncpus && ((*rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 1011 CMD_RCM_CAP_ADD, ncpus)) != RCM_SUCCESS)) 1012 return (CFGA_LIB_ERROR); 1013 if (npages && ((*rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 1014 CMD_RCM_CAP_ADD, npages)) != RCM_SUCCESS)) 1015 return (CFGA_LIB_ERROR); 1016 1017 return (CFGA_OK); 1018 } 1019 1020 /* 1021 * ap_rcm_notify_cap: 1022 * 1023 * This routine handles the CMD_RCM_CAP_NOTIFY command. It 1024 * is called after a successful/failed DR unconfigure 1025 * operation. It filters out components that have changed 1026 * and passes this information on to ap_rcm_cap_{cpu,mem}. 1027 * 1028 * ap_rcm_cap_{cpu,mem} will still be called if all the 1029 * components have not changed and at least one {cpu,mem} 1030 * component was originally configured. 1031 */ 1032 static cfga_err_t 1033 ap_rcm_notify_cap(apd_t *a, rcmd_t *rcm, rcm_handle_t *hd, 1034 int *rv, uint_t flags, rcm_info_t **rinfo) 1035 { 1036 cfga_err_t rc; 1037 cap_info_t *capinfo; 1038 cap_info_t *prevcapinfo; 1039 int cm; 1040 long npages = 0; 1041 int ncpus = 0; 1042 int prev_mem = 0; /* # of prev. configured mem components */ 1043 int prev_cpus = 0; /* # of prev. configured CPUs */ 1044 1045 DBG("ap_rcm_notify_cap(%p)\n", (void *)a); 1046 1047 /* Get the new capacity info to figure out what has changed */ 1048 if ((rc = ap_capinfo(a, rcm->firstcm, rcm->lastcm, &capinfo)) 1049 != CFGA_OK) 1050 return (rc); 1051 1052 if (capinfo == NULL) { 1053 DBG("no pertinent capacity info\n"); 1054 return (CFGA_OK); 1055 } 1056 1057 /* The original capacity info */ 1058 prevcapinfo = rcm->capinfo; 1059 1060 /* 1061 * Cycle through all components that we are operating 1062 * on. Record which components' occupant states have 1063 * changed. 1064 */ 1065 for (cm = rcm->firstcm; cm <= rcm->lastcm; cm++) { 1066 int i; 1067 cfga_stat_t prevos, os; 1068 ap_target_t type; 1069 int prev_conf = 0; 1070 int now_conf = 0; 1071 1072 /* 1073 * See if the request target is a single 1074 * (default) component 1075 */ 1076 i = cm == CM_DFLT ? 0 : cm; 1077 1078 os = capinfo[i].ostate; 1079 1080 if (prevcapinfo == NULL) { 1081 prevos = CFGA_STAT_EMPTY; 1082 } else { 1083 prevos = prevcapinfo[i].ostate; 1084 if (prevcapinfo[i].valid == 0) { 1085 DBG("ap_rcm_notify_cap: skipping component " 1086 "due to prevvalidity == 0\n"); 1087 continue; 1088 } 1089 } 1090 1091 type = ap_cm_type(a, cm); 1092 1093 prev_conf = (prevos == CFGA_STAT_CONFIGURED); 1094 now_conf = (os == CFGA_STAT_CONFIGURED); 1095 1096 /* 1097 * Build up rcm->cpuids with the IDs of CPUs that 1098 * have been removed. Record the number of removed 1099 * CPUs and pages. 1100 */ 1101 if (type == AP_CPU || type == AP_CMP) { 1102 if (prev_conf) 1103 prev_cpus++; 1104 if (prev_conf && !now_conf) { 1105 int j; 1106 for (j = 0; j < capinfo[i].ncap; j++) { 1107 rcm->cpuids[ncpus++] = 1108 capinfo[i].type.cpuid[j]; 1109 } 1110 } 1111 } else if (type == AP_MEM) { 1112 if (prev_conf) 1113 prev_mem++; 1114 if (prev_conf && !now_conf) 1115 npages += capinfo[i].type.npages; 1116 } 1117 } 1118 free(capinfo); 1119 1120 /* 1121 * If any CPU or memory components were operated on, 1122 * successfully or not, the rcm_notify_capacity_change() 1123 * routine must be called. 1124 */ 1125 1126 if (prev_cpus) { 1127 *rv = ap_rcm_cap_cpu(a, rcm, hd, flags, rinfo, 1128 CMD_RCM_CAP_NOTIFY, ncpus); 1129 1130 if (*rv != RCM_SUCCESS) 1131 return (CFGA_LIB_ERROR); 1132 } 1133 1134 if (prev_mem) { 1135 *rv = ap_rcm_cap_mem(a, rcm, hd, flags, rinfo, 1136 CMD_RCM_CAP_NOTIFY, npages); 1137 1138 if (*rv != RCM_SUCCESS) 1139 return (CFGA_LIB_ERROR); 1140 } 1141 1142 return (CFGA_OK); 1143 } 1144 1145 cfga_err_t 1146 ap_rcm_ctl(apd_t *a, int cmd) 1147 { 1148 int i; 1149 int rv; 1150 int noop; 1151 int ncpus; 1152 int cm; 1153 uint_t flags; 1154 char *rsrc; 1155 char **rlist; 1156 rcmd_t *rcm; 1157 rcm_info_t *rinfo; 1158 rcm_handle_t *hd; 1159 cfga_err_t rc; 1160 cpuid_t *growcpuids; 1161 1162 DBG("ap_rcm_ctl(%p)\n", (void *)a); 1163 1164 if ((rcm = (rcmd_t *)a->rcm) == NULL) { 1165 ap_msg(a, MSG_SKIP, cmd, a->target); 1166 return (CFGA_OK); 1167 } 1168 1169 hd = rcm->hd; 1170 rv = RCM_SUCCESS; 1171 rc = CFGA_OK; 1172 if (ap_getopt(a, OPT_FORCE)) 1173 flags = RCM_FORCE; 1174 else 1175 flags = 0; 1176 rinfo = NULL; 1177 rlist = NULL; 1178 rsrc = NULL; 1179 noop = 0; 1180 1181 switch (cmd) { 1182 case CMD_RCM_CAP_DEL: 1183 if (rcm->capinfo == NULL) 1184 noop++; 1185 else 1186 rc = ap_rcm_request_cap(a, rcm, hd, &rv, 1187 flags, &rinfo); 1188 break; 1189 case CMD_RCM_CAP_ADD: 1190 rc = ap_rcm_add_cap(a, rcm, hd, &rv, flags, &rinfo); 1191 break; 1192 case CMD_RCM_CAP_NOTIFY: 1193 rc = ap_rcm_notify_cap(a, rcm, hd, &rv, flags, &rinfo); 1194 break; 1195 case CMD_RCM_ONLINE: 1196 /* Refresh changed component states */ 1197 if ((rc = ap_stat(a, 1)) != CFGA_OK) { 1198 noop++; 1199 break; 1200 } 1201 1202 if (a->tgt == AP_BOARD) { 1203 rcm->firstcm = 0; 1204 rcm->lastcm = a->ncm - 1; 1205 1206 /* Check if we need to grow our cpuids list */ 1207 for (ncpus = 0, cm = rcm->firstcm; cm <= rcm->lastcm; 1208 cm++) { 1209 ap_target_t type = ap_cm_type(a, cm); 1210 if ((type == AP_CPU) || (type == AP_CMP)) 1211 ncpus += ap_cm_ncap(a, cm); 1212 } 1213 1214 if (rcm->ncpus < ncpus) { 1215 if ((growcpuids = 1216 (cpuid_t *)realloc(rcm->cpuids, 1217 (ncpus * sizeof (cpuid_t)))) == NULL) { 1218 ap_err(a, ERR_NOMEM); 1219 return (CFGA_LIB_ERROR); 1220 } 1221 rcm->ncpus = ncpus; 1222 rcm->cpuids = growcpuids; 1223 } 1224 1225 } else { 1226 rcm->firstcm = CM_DFLT; 1227 rcm->lastcm = CM_DFLT; 1228 } 1229 1230 /*FALLTHROUGH*/ 1231 1232 case CMD_RCM_OFFLINE: 1233 case CMD_RCM_REMOVE: { 1234 uint_t nrsrc; 1235 1236 if (cmd == CMD_RCM_REMOVE) { 1237 /* 1238 * An unconfigure has just taken place, so 1239 * refresh the changed component states. 1240 */ 1241 if ((rc = ap_stat(a, 1)) != CFGA_OK) { 1242 noop++; 1243 break; 1244 } 1245 } 1246 1247 /* Check if this is an empty board, i.e. no components */ 1248 if (a->ncm == 0) { 1249 noop++; 1250 break; 1251 } 1252 1253 if ((rlist = rcm->rlist) == NULL) { 1254 rc = ap_rcm_rlist(a, rcm->firstcm, rcm->lastcm, &rlist, 1255 cmd); 1256 if ((rc == CFGA_OK) && (rlist != NULL) && 1257 (rlist[0] != NULL)) { 1258 rcm->rlist = rlist; 1259 } else { 1260 /* Do not pass up empty resource list to RCM */ 1261 noop++; 1262 break; 1263 } 1264 } 1265 for (nrsrc = 0; rlist[nrsrc] != NULL; nrsrc++) 1266 ap_msg(a, MSG_ISSUE, cmd, rlist[nrsrc]); 1267 if (cmd == CMD_RCM_OFFLINE) 1268 rv = (*rcm->request_offline_list)(hd, rlist, flags, 1269 &rinfo); 1270 else if (cmd == CMD_RCM_ONLINE) 1271 rv = (*rcm->notify_online_list)(hd, rlist, 1272 flags & ~RCM_FORCE, &rinfo); 1273 else 1274 rv = (*rcm->notify_remove_list)(hd, rlist, 1275 flags & ~RCM_FORCE, &rinfo); 1276 break; 1277 } 1278 case CMD_RCM_SUSPEND: { 1279 timespec_t t; 1280 t.tv_sec = (time_t)0; 1281 t.tv_nsec = (long)0; 1282 rsrc = OS; 1283 ap_msg(a, MSG_ISSUE, cmd, rsrc); 1284 rv = (*rcm->request_suspend)(hd, rsrc, flags, &t, &rinfo); 1285 break; 1286 } 1287 case CMD_RCM_RESUME: 1288 rsrc = OS; 1289 ap_msg(a, MSG_ISSUE, cmd, rsrc); 1290 rv = (*rcm->notify_resume)(hd, rsrc, 0, &rinfo); 1291 break; 1292 default: 1293 ap_err(a, ERR_CMD_INVAL, cmd); 1294 return (CFGA_INVAL); 1295 } 1296 1297 if (rv != RCM_SUCCESS) { 1298 rcm->rinfo = rinfo; 1299 rcm->infot = NULL; 1300 ap_err(a, ERR_RCM_CMD, cmd); 1301 (*rcm->free_info)(rinfo); 1302 if (rc == CFGA_OK) 1303 rc = CFGA_LIB_ERROR; /* make sure error is set */ 1304 } 1305 if ((rc == CFGA_OK) && (noop == 0)) { 1306 if (rlist) 1307 for (i = 0; rlist[i]; i++) 1308 ap_msg(a, MSG_DONE, cmd, rlist[i]); 1309 else if (rsrc) 1310 ap_msg(a, MSG_DONE, cmd, rsrc); 1311 else 1312 ap_msg(a, MSG_DONE, cmd, a->target); 1313 } 1314 1315 return (rc); 1316 } 1317 1318 /* 1319 * ap_rcm_info 1320 * 1321 * Takes an ap_id and a character pointer, and formats 1322 * the rcm_info_t data in the form of a table to the given character pointer. 1323 * Code duplicated from the scsi plugin. 1324 * Note: This function will go away when a generic librcm callback is 1325 * implemented to format RCM messages for plugins. 1326 */ 1327 int 1328 ap_rcm_info(apd_t *a, char **msg) 1329 { 1330 rcmd_t *rcm; 1331 rcm_info_t *rinfo; 1332 int i; 1333 size_t w; 1334 size_t width = 0; 1335 size_t w_rsrc = 0; 1336 size_t w_info = 0; 1337 size_t msg_size = 0; 1338 uint_t tuples = 0; 1339 rcm_info_tuple_t *tuple = NULL; 1340 char *rsrc; 1341 char *info; 1342 char *newmsg; 1343 static char format[RCM_MAX_FORMAT]; 1344 const char *infostr; 1345 1346 1347 DBG("ap_rcm_info(%p)\n", (void *)a); 1348 1349 /* Protect against invalid arguments */ 1350 if ((a == NULL) || ((rcm = (rcmd_t *)a->rcm) == NULL) || 1351 ((rinfo = rcm->rinfo) == NULL) || (msg == NULL)) { 1352 return (-1); 1353 } 1354 1355 /* Set localized table header strings */ 1356 rsrc = dgettext(TEXT_DOMAIN, "Resource"); 1357 info = dgettext(TEXT_DOMAIN, "Information"); 1358 1359 /* A first pass, to size up the RCM information */ 1360 while (tuple = (*rcm->info_next)(rinfo, tuple)) { 1361 if ((infostr = (*rcm->info_info)(tuple)) != NULL) { 1362 tuples++; 1363 if ((w = strlen((*rcm->info_rsrc)(tuple))) > w_rsrc) 1364 w_rsrc = w; 1365 if ((w = strlen(infostr)) > w_info) 1366 w_info = w; 1367 } 1368 } 1369 1370 /* If nothing was sized up above, stop early */ 1371 if (tuples == 0) 1372 return (0); 1373 1374 /* Adjust column widths for column headings */ 1375 if ((w = strlen(rsrc)) > w_rsrc) 1376 w_rsrc = w; 1377 else if ((w_rsrc - w) % 2) 1378 w_rsrc++; 1379 if ((w = strlen(info)) > w_info) 1380 w_info = w; 1381 else if ((w_info - w) % 2) 1382 w_info++; 1383 1384 /* 1385 * Compute the total line width of each line, 1386 * accounting for intercolumn spacing. 1387 */ 1388 width = w_info + w_rsrc + 4; 1389 1390 /* Allocate space for the table */ 1391 msg_size = (2 + tuples) * (width + 1) + 2; 1392 if (*msg == NULL) { 1393 /* zero fill for the strcat() call below */ 1394 *msg = calloc(msg_size, sizeof (char)); 1395 if (*msg == NULL) 1396 return (-1); 1397 } else { 1398 newmsg = realloc(*msg, strlen(*msg) + msg_size); 1399 if (newmsg == NULL) 1400 return (-1); 1401 else 1402 *msg = newmsg; 1403 } 1404 1405 /* Place a table header into the string */ 1406 1407 /* The resource header */ 1408 (void) strcat(*msg, "\n"); 1409 w = strlen(rsrc); 1410 for (i = 0; i < ((w_rsrc - w) / 2); i++) 1411 (void) strcat(*msg, " "); 1412 (void) strcat(*msg, rsrc); 1413 for (i = 0; i < ((w_rsrc - w) / 2); i++) 1414 (void) strcat(*msg, " "); 1415 1416 /* The information header */ 1417 (void) strcat(*msg, " "); 1418 w = strlen(info); 1419 for (i = 0; i < ((w_info - w) / 2); i++) 1420 (void) strcat(*msg, " "); 1421 (void) strcat(*msg, info); 1422 for (i = 0; i < ((w_info - w) / 2); i++) 1423 (void) strcat(*msg, " "); 1424 1425 /* Underline the headers */ 1426 (void) strcat(*msg, "\n"); 1427 for (i = 0; i < w_rsrc; i++) 1428 (void) strcat(*msg, "-"); 1429 (void) strcat(*msg, " "); 1430 for (i = 0; i < w_info; i++) 1431 (void) strcat(*msg, "-"); 1432 1433 /* Construct the format string */ 1434 (void) snprintf(format, RCM_MAX_FORMAT, "%%-%ds %%-%ds", 1435 (int)w_rsrc, (int)w_info); 1436 1437 /* Add the tuples to the table string */ 1438 tuple = NULL; 1439 while ((tuple = (*rcm->info_next)(rinfo, tuple)) != NULL) { 1440 if ((infostr = (*rcm->info_info)(tuple)) != NULL) { 1441 (void) strcat(*msg, "\n"); 1442 (void) sprintf(&((*msg)[strlen(*msg)]), format, 1443 (*rcm->info_rsrc)(tuple), infostr); 1444 } 1445 } 1446 1447 DBG("ap_rcm_info(%p) success\n", (void *)a); 1448 return (0); 1449 } 1450