1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Copyright 2026 Oxide Computer Company 29 */ 30 31 #include "lint.h" 32 #include "thr_uberdata.h" 33 #include <sched.h> 34 #include <spawn.h> 35 #include <stdbool.h> 36 #include <sys/tspriocntl.h> 37 #include <sys/rtpriocntl.h> 38 #include <sys/fxpriocntl.h> 39 #include <sys/spawn_impl.h> 40 41 /* 42 * The following array is used for caching information 43 * for priocntl scheduling classes. 44 */ 45 static pcclass_t sched_class[] = { 46 {0, SCHED_OTHER, 0, 0, {-1, "TS", 0}}, 47 {0, SCHED_FIFO, 0, 0, {-1, "RT", 0}}, 48 {0, SCHED_RR, 0, 0, {-1, "RT", 0}}, 49 {0, SCHED_SYS, 0, 0, {0, "SYS", 0}}, 50 {0, SCHED_IA, 0, 0, {-1, "IA", 0}}, 51 {0, SCHED_FSS, 0, 0, {-1, "FSS", 0}}, 52 {0, SCHED_FX, 0, 0, {-1, "FX", 0}}, 53 /* 54 * Allow unknown (to us) scheduling classes. 55 * The kernel allows space for exactly 10 scheduling classes 56 * (see the definitions of 'sclass' and 'nclass' in the kernel). 57 * We need that number of available slots here. 58 * If the kernel space is changed, this has to change too. 59 */ 60 {0, -1, 0, 0, {-1, "", 0}}, 61 {0, -1, 0, 0, {-1, "", 0}}, 62 {0, -1, 0, 0, {-1, "", 0}}, 63 {0, -1, 0, 0, {-1, "", 0}}, 64 {0, -1, 0, 0, {-1, "", 0}}, 65 {0, -1, 0, 0, {-1, "", 0}}, 66 {0, -1, 0, 0, {-1, "", 0}}, 67 {0, -1, 0, 0, {-1, "", 0}}, 68 {0, -1, 0, 0, {-1, "", 0}}, 69 {0, -1, 0, 0, {-1, "", 0}}, 70 }; 71 72 #define NPOLICY (sizeof (sched_class) / sizeof (pcclass_t)) 73 74 #if _SCHED_NEXT != SCHED_FX + 1 75 #error "fatal: _SCHED_NEXT != SCHED_FX + 1" 76 #endif 77 78 static mutex_t class_lock = DEFAULTMUTEX; /* protects sched_class[] */ 79 80 /* 81 * Helper function for get_info_by_policy(), below. 82 * Don't let a manufactured policy number duplicate 83 * the class of one of our base policy numbers. 84 */ 85 static int 86 is_base_class(const char *clname) 87 { 88 const pcclass_t *pccp; 89 int policy; 90 91 for (policy = 0, pccp = sched_class; 92 policy < _SCHED_NEXT; 93 policy++, pccp++) { 94 if (strcmp(clname, pccp->pcc_info.pc_clname) == 0) 95 return (1); 96 } 97 return (0); 98 } 99 100 /* 101 * Cache priocntl information on scheduling class by policy. 102 */ 103 const pcclass_t * 104 get_info_by_policy(int policy) 105 { 106 pcclass_t *pccp = &sched_class[policy]; 107 pcpri_t pcpri; 108 pri_t prio; 109 int base = 0; 110 111 if ((uint_t)policy >= NPOLICY || pccp->pcc_state < 0) { 112 errno = EINVAL; 113 return (NULL); 114 } 115 116 if (pccp->pcc_state > 0) 117 return (pccp); 118 119 lmutex_lock(&class_lock); 120 121 /* get class info (the system class is known to have class-id == 0) */ 122 if (pccp->pcc_policy == -1) { 123 /* policy number not defined in <sched.h> */ 124 ASSERT(policy >= _SCHED_NEXT); 125 pccp->pcc_info.pc_cid = policy - _SCHED_NEXT; 126 if (priocntl(0, 0, PC_GETCLINFO, &pccp->pcc_info) == -1 || 127 (base = is_base_class(pccp->pcc_info.pc_clname)) != 0) { 128 pccp->pcc_info.pc_clname[0] = '\0'; 129 pccp->pcc_info.pc_cid = -1; 130 /* 131 * If we duplicated a base class, permanently 132 * disable this policy entry. Else allow for 133 * dynamic loading of scheduling classes. 134 */ 135 if (base) { 136 membar_producer(); 137 pccp->pcc_state = -1; 138 } 139 errno = EINVAL; 140 lmutex_unlock(&class_lock); 141 return (NULL); 142 } 143 pccp->pcc_policy = policy; 144 } else if (policy != SCHED_SYS && 145 priocntl(0, 0, PC_GETCID, &pccp->pcc_info) == -1) { 146 membar_producer(); 147 pccp->pcc_state = -1; 148 errno = EINVAL; 149 lmutex_unlock(&class_lock); 150 return (NULL); 151 } 152 153 switch (policy) { 154 case SCHED_OTHER: 155 prio = ((tsinfo_t *)pccp->pcc_info.pc_clinfo)->ts_maxupri; 156 pccp->pcc_primin = -prio; 157 pccp->pcc_primax = prio; 158 break; 159 case SCHED_FIFO: 160 case SCHED_RR: 161 prio = ((rtinfo_t *)pccp->pcc_info.pc_clinfo)->rt_maxpri; 162 pccp->pcc_primin = 0; 163 pccp->pcc_primax = prio; 164 break; 165 default: 166 /* 167 * All other policy numbers, including policy numbers 168 * not defined in <sched.h>. 169 */ 170 pcpri.pc_cid = pccp->pcc_info.pc_cid; 171 if (priocntl(0, 0, PC_GETPRIRANGE, &pcpri) == 0) { 172 pccp->pcc_primin = pcpri.pc_clpmin; 173 pccp->pcc_primax = pcpri.pc_clpmax; 174 } 175 break; 176 } 177 178 membar_producer(); 179 pccp->pcc_state = 1; 180 lmutex_unlock(&class_lock); 181 return (pccp); 182 } 183 184 const pcclass_t * 185 get_info_by_class(id_t classid) 186 { 187 pcinfo_t pcinfo; 188 pcclass_t *pccp; 189 int policy; 190 191 if (classid < 0) { 192 errno = EINVAL; 193 return (NULL); 194 } 195 196 /* determine if we already know this classid */ 197 for (policy = 0, pccp = sched_class; 198 policy < NPOLICY; 199 policy++, pccp++) { 200 if (pccp->pcc_state > 0 && pccp->pcc_info.pc_cid == classid) 201 return (pccp); 202 } 203 204 pcinfo.pc_cid = classid; 205 if (priocntl(0, 0, PC_GETCLINFO, &pcinfo) == -1) { 206 if (classid == 0) /* no kernel info for sys class */ 207 return (get_info_by_policy(SCHED_SYS)); 208 return (NULL); 209 } 210 211 for (policy = 0, pccp = sched_class; 212 policy < NPOLICY; 213 policy++, pccp++) { 214 if (pccp->pcc_state == 0 && 215 strcmp(pcinfo.pc_clname, pccp->pcc_info.pc_clname) == 0) 216 return (get_info_by_policy(pccp->pcc_policy)); 217 } 218 219 /* 220 * We have encountered an unknown (to us) scheduling class. 221 * Manufacture a policy number for it. Hopefully we still 222 * have room in the sched_class[] table. 223 */ 224 policy = _SCHED_NEXT + classid; 225 if (policy >= NPOLICY) { 226 errno = EINVAL; 227 return (NULL); 228 } 229 lmutex_lock(&class_lock); 230 pccp = &sched_class[policy]; 231 pccp->pcc_policy = policy; 232 (void) strlcpy(pccp->pcc_info.pc_clname, pcinfo.pc_clname, PC_CLNMSZ); 233 lmutex_unlock(&class_lock); 234 return (get_info_by_policy(pccp->pcc_policy)); 235 } 236 237 /* 238 * Helper function: get process or lwp current scheduling policy. 239 */ 240 static const pcclass_t * 241 get_parms(idtype_t idtype, id_t id, pcparms_t *pcparmp) 242 { 243 pcparmp->pc_cid = PC_CLNULL; 244 if (priocntl(idtype, id, PC_GETPARMS, pcparmp) == -1) 245 return (NULL); 246 return (get_info_by_class(pcparmp->pc_cid)); 247 } 248 249 /* 250 * Helper function for set_priority() and spawn_sched_resolve(), below. 251 * Resolve a policy and priority into the form to be applied to the 252 * target. 253 */ 254 static int 255 resolve_priority(int policy, int prio, int settq, pcparms_t *pcparmp, 256 pcprio_t *pcpriop) 257 { 258 switch (policy) { 259 case SCHED_OTHER: 260 { 261 tsparms_t *tsp = (tsparms_t *)pcparmp->pc_clparms; 262 263 tsp->ts_uprilim = prio; 264 tsp->ts_upri = prio; 265 return (PC_SETPARMS); 266 } 267 case SCHED_FIFO: 268 case SCHED_RR: 269 { 270 rtparms_t *rtp = (rtparms_t *)pcparmp->pc_clparms; 271 272 rtp->rt_tqnsecs = settq? 273 (policy == SCHED_FIFO? RT_TQINF : RT_TQDEF) : 274 RT_NOCHANGE; 275 rtp->rt_pri = prio; 276 return (PC_SETPARMS); 277 } 278 default: 279 { 280 /* 281 * Class-independent method for setting the priority. 282 */ 283 pcpriop->pc_op = PC_SETPRIO; 284 pcpriop->pc_cid = pcparmp->pc_cid; 285 pcpriop->pc_val = prio; 286 return (PC_DOPRIO); 287 } 288 } 289 } 290 291 /* 292 * Helper function for setprio() and setparam(), below. 293 */ 294 static int 295 set_priority(idtype_t idtype, id_t id, int policy, int prio, 296 pcparms_t *pcparmp, int settq) 297 { 298 pcprio_t pcprio; 299 caddr_t arg; 300 int cmd, rv; 301 302 cmd = resolve_priority(policy, prio, settq, pcparmp, &pcprio); 303 arg = (cmd == PC_SETPARMS) ? (caddr_t)pcparmp : (caddr_t)&pcprio; 304 305 do { 306 rv = priocntl(idtype, id, cmd, arg); 307 } while (rv == -1 && errno == ENOMEM); 308 309 return (rv); 310 } 311 312 /* 313 * Utility function, private to libc, used by sched_setparam() 314 * and posix_spawn(). Because it is called by the vfork() child of 315 * posix_spawn(), we must not call any functions exported from libc. 316 */ 317 id_t 318 setprio(idtype_t idtype, id_t id, int prio, int *policyp) 319 { 320 pcparms_t pcparm; 321 int policy; 322 const pcclass_t *pccp; 323 324 if ((pccp = get_parms(idtype, id, &pcparm)) == NULL) 325 return (-1); 326 if (prio < pccp->pcc_primin || prio > pccp->pcc_primax) { 327 errno = EINVAL; 328 return (-1); 329 } 330 331 policy = pccp->pcc_policy; 332 if (policyp != NULL && 333 (policy == SCHED_FIFO || policy == SCHED_RR)) { 334 rtparms_t *rtp = (rtparms_t *)pcparm.pc_clparms; 335 policy = (rtp->rt_tqnsecs == RT_TQINF? SCHED_FIFO : SCHED_RR); 336 } 337 338 if (set_priority(idtype, id, policy, prio, &pcparm, 0) == -1) 339 return (-1); 340 if (policyp != NULL) 341 *policyp = policy; 342 return (pccp->pcc_info.pc_cid); 343 } 344 345 int 346 sched_setparam(pid_t pid, const struct sched_param *param) 347 { 348 if (pid < 0) { 349 errno = ESRCH; 350 return (-1); 351 } 352 if (pid == 0) 353 pid = P_MYID; 354 355 if (setprio(P_PID, pid, param->sched_priority, NULL) == -1) 356 return (-1); 357 return (0); 358 } 359 360 id_t 361 getparam(idtype_t idtype, id_t id, int *policyp, struct sched_param *param) 362 { 363 pcparms_t pcparm; 364 const pcclass_t *pccp; 365 int policy; 366 int priority; 367 368 if ((pccp = get_parms(idtype, id, &pcparm)) == NULL) 369 return (-1); 370 371 switch (policy = pccp->pcc_policy) { 372 case SCHED_OTHER: 373 { 374 tsparms_t *tsp = (tsparms_t *)pcparm.pc_clparms; 375 priority = tsp->ts_upri; 376 break; 377 } 378 case SCHED_FIFO: 379 case SCHED_RR: 380 { 381 rtparms_t *rtp = (rtparms_t *)pcparm.pc_clparms; 382 priority = rtp->rt_pri; 383 policy = (rtp->rt_tqnsecs == RT_TQINF? SCHED_FIFO : SCHED_RR); 384 break; 385 } 386 default: 387 { 388 /* 389 * Class-independent method for getting the priority. 390 */ 391 pcprio_t pcprio; 392 393 pcprio.pc_op = PC_GETPRIO; 394 pcprio.pc_cid = 0; 395 pcprio.pc_val = 0; 396 if (priocntl(idtype, id, PC_DOPRIO, &pcprio) == 0) 397 priority = pcprio.pc_val; 398 else 399 priority = 0; 400 break; 401 } 402 } 403 404 *policyp = policy; 405 (void) memset(param, 0, sizeof (*param)); 406 param->sched_priority = priority; 407 408 return (pcparm.pc_cid); 409 } 410 411 int 412 sched_getparam(pid_t pid, struct sched_param *param) 413 { 414 int policy; 415 416 if (pid < 0) { 417 errno = ESRCH; 418 return (-1); 419 } 420 if (pid == 0) 421 pid = P_MYID; 422 423 if (getparam(P_PID, pid, &policy, param) == -1) 424 return (-1); 425 return (0); 426 } 427 428 /* 429 * Utility function, private to libc, used by sched_setscheduler() 430 * and posix_spawn(). Because it is called by the vfork() child of 431 * posix_spawn(), we must not call any functions exported from libc. 432 */ 433 id_t 434 setparam(idtype_t idtype, id_t id, int policy, int prio) 435 { 436 pcparms_t pcparm; 437 const pcclass_t *pccp; 438 439 if (policy == SCHED_SYS || 440 (pccp = get_info_by_policy(policy)) == NULL || 441 prio < pccp->pcc_primin || prio > pccp->pcc_primax) { 442 errno = EINVAL; 443 return (-1); 444 } 445 446 pcparm.pc_cid = pccp->pcc_info.pc_cid; 447 if (set_priority(idtype, id, policy, prio, &pcparm, 1) == -1) 448 return (-1); 449 return (pccp->pcc_info.pc_cid); 450 } 451 452 /* 453 * Utility function, private to libc, used by posix_spawn() when the 454 * scheduler attribute flags are in effect. It performs the same validation and 455 * resolution that setparam() or setprio() above would, but the target of the 456 * change (the spawn(2) child) does not yet exist so the result is handed back 457 * ready for passing to the kernel. 458 */ 459 int 460 spawn_sched_resolve(int psflags, int policy, int prio, kspawn_sched_t *ks) 461 { 462 bool setsched = (psflags & POSIX_SPAWN_SETSCHEDULER) != 0; 463 pcparms_t *pcparmp = &ks->ksched_parms; 464 const pcclass_t *pccp; 465 pcprio_t pcprio; 466 467 (void) memset(ks, 0, sizeof (*ks)); 468 469 if (setsched) { 470 if (policy == SCHED_SYS || 471 (pccp = get_info_by_policy(policy)) == NULL) 472 return (EINVAL); 473 pcparmp->pc_cid = pccp->pcc_info.pc_cid; 474 } else { 475 /* 476 * The priority change is applied within the scheduling 477 * class that the child inherits from this process. 478 */ 479 if ((pccp = get_parms(P_LWPID, P_MYID, pcparmp)) == NULL) 480 return (errno); 481 policy = pccp->pcc_policy; 482 } 483 484 if (prio < pccp->pcc_primin || prio > pccp->pcc_primax) 485 return (EINVAL); 486 487 if (resolve_priority(policy, prio, setsched, pcparmp, 488 &pcprio) == PC_SETPARMS) { 489 ks->ksched_op = KSCHED_PARMS; 490 } else { 491 ks->ksched_op = KSCHED_PRIO; 492 ks->ksched_prio = pcprio; 493 } 494 495 return (0); 496 } 497 498 int 499 sched_setscheduler(pid_t pid, int policy, const struct sched_param *param) 500 { 501 pri_t prio = param->sched_priority; 502 int oldpolicy; 503 504 if ((oldpolicy = sched_getscheduler(pid)) < 0) 505 return (-1); 506 507 if (pid == 0) 508 pid = P_MYID; 509 510 if (setparam(P_PID, pid, policy, prio) == -1) 511 return (-1); 512 513 return (oldpolicy); 514 } 515 516 int 517 sched_getscheduler(pid_t pid) 518 { 519 pcparms_t pcparm; 520 const pcclass_t *pccp; 521 int policy; 522 523 if (pid < 0) { 524 errno = ESRCH; 525 return (-1); 526 } 527 if (pid == 0) 528 pid = P_MYID; 529 530 if ((pccp = get_parms(P_PID, pid, &pcparm)) == NULL) 531 return (-1); 532 533 if ((policy = pccp->pcc_policy) == SCHED_FIFO || policy == SCHED_RR) { 534 policy = 535 (((rtparms_t *)pcparm.pc_clparms)->rt_tqnsecs == RT_TQINF? 536 SCHED_FIFO : SCHED_RR); 537 } 538 539 return (policy); 540 } 541 542 int 543 sched_yield(void) 544 { 545 yield(); 546 return (0); 547 } 548 549 int 550 sched_get_priority_max(int policy) 551 { 552 const pcclass_t *pccp; 553 554 if ((pccp = get_info_by_policy(policy)) != NULL) 555 return (pccp->pcc_primax); 556 errno = EINVAL; 557 return (-1); 558 } 559 560 int 561 sched_get_priority_min(int policy) 562 { 563 const pcclass_t *pccp; 564 565 if ((pccp = get_info_by_policy(policy)) != NULL) 566 return (pccp->pcc_primin); 567 errno = EINVAL; 568 return (-1); 569 } 570 571 int 572 sched_rr_get_interval(pid_t pid, timespec_t *interval) 573 { 574 pcparms_t pcparm; 575 const pcclass_t *pccp; 576 577 if (pid < 0) { 578 errno = ESRCH; 579 return (-1); 580 } 581 if (pid == 0) 582 pid = P_MYID; 583 584 if ((pccp = get_parms(P_PID, pid, &pcparm)) == NULL) 585 return (-1); 586 587 /* 588 * At the moment, we have no class-independent method to fetch 589 * the process/lwp time quantum. Since SUSv3 does not restrict 590 * this operation to the real-time class, we return an indefinite 591 * quantum (tv_sec == 0 and tv_nsec == 0) for scheduling policies 592 * for which this information isn't available. 593 */ 594 interval->tv_sec = 0; 595 interval->tv_nsec = 0; 596 597 switch (pccp->pcc_policy) { 598 case SCHED_FIFO: 599 case SCHED_RR: 600 { 601 rtparms_t *rtp = (rtparms_t *)pcparm.pc_clparms; 602 if (rtp->rt_tqnsecs != RT_TQINF) { 603 interval->tv_sec = rtp->rt_tqsecs; 604 interval->tv_nsec = rtp->rt_tqnsecs; 605 } 606 } 607 break; 608 case SCHED_FX: 609 { 610 fxparms_t *fxp = (fxparms_t *)pcparm.pc_clparms; 611 if (fxp->fx_tqnsecs != FX_TQINF) { 612 interval->tv_sec = fxp->fx_tqsecs; 613 interval->tv_nsec = fxp->fx_tqnsecs; 614 } 615 } 616 break; 617 } 618 619 return (0); 620 } 621 622 /* 623 * Initialize or update ul_policy, ul_cid, and ul_pri. 624 */ 625 void 626 update_sched(ulwp_t *self) 627 { 628 volatile sc_shared_t *scp; 629 pcparms_t pcparm; 630 pcprio_t pcprio; 631 const pcclass_t *pccp; 632 int priority; 633 int policy; 634 635 ASSERT(self == curthread); 636 637 enter_critical(self); 638 639 if ((scp = self->ul_schedctl) == NULL && 640 (scp = setup_schedctl()) == NULL) { /* can't happen? */ 641 if (self->ul_policy < 0) { 642 self->ul_cid = 0; 643 self->ul_pri = 0; 644 membar_producer(); 645 self->ul_policy = SCHED_OTHER; 646 } 647 exit_critical(self); 648 return; 649 } 650 651 if (self->ul_policy >= 0 && 652 self->ul_cid == scp->sc_cid && 653 (self->ul_pri == scp->sc_cpri || 654 (self->ul_epri > 0 && self->ul_epri == scp->sc_cpri))) { 655 exit_critical(self); 656 return; 657 } 658 659 pccp = get_parms(P_LWPID, P_MYID, &pcparm); 660 if (pccp == NULL) { /* can't happen? */ 661 self->ul_cid = scp->sc_cid; 662 self->ul_pri = scp->sc_cpri; 663 membar_producer(); 664 self->ul_policy = SCHED_OTHER; 665 exit_critical(self); 666 return; 667 } 668 669 switch (policy = pccp->pcc_policy) { 670 case SCHED_OTHER: 671 priority = ((tsparms_t *)pcparm.pc_clparms)->ts_upri; 672 break; 673 case SCHED_FIFO: 674 case SCHED_RR: 675 self->ul_rtclassid = pccp->pcc_info.pc_cid; 676 priority = ((rtparms_t *)pcparm.pc_clparms)->rt_pri; 677 policy = 678 ((rtparms_t *)pcparm.pc_clparms)->rt_tqnsecs == RT_TQINF? 679 SCHED_FIFO : SCHED_RR; 680 break; 681 default: 682 /* 683 * Class-independent method for getting the priority. 684 */ 685 pcprio.pc_op = PC_GETPRIO; 686 pcprio.pc_cid = 0; 687 pcprio.pc_val = 0; 688 if (priocntl(P_LWPID, P_MYID, PC_DOPRIO, &pcprio) == 0) 689 priority = pcprio.pc_val; 690 else 691 priority = 0; 692 } 693 694 self->ul_cid = pcparm.pc_cid; 695 self->ul_pri = priority; 696 membar_producer(); 697 self->ul_policy = policy; 698 699 exit_critical(self); 700 } 701