1/* Thread Priority Protect helpers.
2 Copyright (C) 2006-2021 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Jakub Jelinek <jakub@redhat.com>, 2006.
5
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, see
18 <https://www.gnu.org/licenses/>. */
19
20#include <assert.h>
21#include <atomic.h>
22#include <errno.h>
23#include <pthreadP.h>
24#include <sched.h>
25#include <stdlib.h>
26#include <atomic.h>
27
28int __sched_fifo_min_prio = -1;
29libc_hidden_data_def (__sched_fifo_min_prio)
30int __sched_fifo_max_prio = -1;
31libc_hidden_data_def (__sched_fifo_max_prio)
32
33/* We only want to initialize __sched_fifo_min_prio and __sched_fifo_max_prio
34 once. The standard solution would be similar to pthread_once, but then
35 readers would need to use an acquire fence. In this specific case,
36 initialization is comprised of just idempotent writes to two variables
37 that have an initial value of -1. Therefore, we can treat each variable as
38 a separate, at-least-once initialized value. This enables using just
39 relaxed MO loads and stores, but requires that consumers check for
40 initialization of each value that is to be used; see
41 __pthread_tpp_change_priority for an example.
42 */
43void
44__init_sched_fifo_prio (void)
45{
46 atomic_store_relaxed (&__sched_fifo_max_prio,
47 __sched_get_priority_max (SCHED_FIFO));
48 atomic_store_relaxed (&__sched_fifo_min_prio,
49 __sched_get_priority_min (SCHED_FIFO));
50}
51libc_hidden_def (__init_sched_fifo_prio)
52
53int
54__pthread_tpp_change_priority (int previous_prio, int new_prio)
55{
56 struct pthread *self = THREAD_SELF;
57 struct priority_protection_data *tpp = THREAD_GETMEM (self, tpp);
58 int fifo_min_prio = atomic_load_relaxed (&__sched_fifo_min_prio);
59 int fifo_max_prio = atomic_load_relaxed (&__sched_fifo_max_prio);
60
61 if (tpp == NULL)
62 {
63 /* See __init_sched_fifo_prio. We need both the min and max prio,
64 so need to check both, and run initialization if either one is
65 not initialized. The memory model's write-read coherence rule
66 makes this work. */
67 if (fifo_min_prio == -1 || fifo_max_prio == -1)
68 {
69 __init_sched_fifo_prio ();
70 fifo_min_prio = atomic_load_relaxed (&__sched_fifo_min_prio);
71 fifo_max_prio = atomic_load_relaxed (&__sched_fifo_max_prio);
72 }
73
74 size_t size = sizeof *tpp;
75 size += (fifo_max_prio - fifo_min_prio + 1)
76 * sizeof (tpp->priomap[0]);
77 tpp = calloc (size, 1);
78 if (tpp == NULL)
79 return ENOMEM;
80 tpp->priomax = fifo_min_prio - 1;
81 THREAD_SETMEM (self, tpp, tpp);
82 }
83
84 assert (new_prio == -1
85 || (new_prio >= fifo_min_prio
86 && new_prio <= fifo_max_prio));
87 assert (previous_prio == -1
88 || (previous_prio >= fifo_min_prio
89 && previous_prio <= fifo_max_prio));
90
91 int priomax = tpp->priomax;
92 int newpriomax = priomax;
93 if (new_prio != -1)
94 {
95 if (tpp->priomap[new_prio - fifo_min_prio] + 1 == 0)
96 return EAGAIN;
97 ++tpp->priomap[new_prio - fifo_min_prio];
98 if (new_prio > priomax)
99 newpriomax = new_prio;
100 }
101
102 if (previous_prio != -1)
103 {
104 if (--tpp->priomap[previous_prio - fifo_min_prio] == 0
105 && priomax == previous_prio
106 && previous_prio > new_prio)
107 {
108 int i;
109 for (i = previous_prio - 1; i >= fifo_min_prio; --i)
110 if (tpp->priomap[i - fifo_min_prio])
111 break;
112 newpriomax = i;
113 }
114 }
115
116 if (priomax == newpriomax)
117 return 0;
118
119 /* See CREATE THREAD NOTES in nptl/pthread_create.c. */
120 lll_lock (self->lock, LLL_PRIVATE);
121
122 tpp->priomax = newpriomax;
123
124 int result = 0;
125
126 if ((self->flags & ATTR_FLAG_SCHED_SET) == 0)
127 {
128 if (__sched_getparam (self->tid, &self->schedparam) != 0)
129 result = errno;
130 else
131 self->flags |= ATTR_FLAG_SCHED_SET;
132 }
133
134 if ((self->flags & ATTR_FLAG_POLICY_SET) == 0)
135 {
136 self->schedpolicy = __sched_getscheduler (self->tid);
137 if (self->schedpolicy == -1)
138 result = errno;
139 else
140 self->flags |= ATTR_FLAG_POLICY_SET;
141 }
142
143 if (result == 0)
144 {
145 struct sched_param sp = self->schedparam;
146 if (sp.sched_priority < newpriomax || sp.sched_priority < priomax)
147 {
148 if (sp.sched_priority < newpriomax)
149 sp.sched_priority = newpriomax;
150
151 if (__sched_setscheduler (self->tid, self->schedpolicy, &sp) < 0)
152 result = errno;
153 }
154 }
155
156 lll_unlock (self->lock, LLL_PRIVATE);
157
158 return result;
159}
160libc_hidden_def (__pthread_tpp_change_priority)
161
162int
163__pthread_current_priority (void)
164{
165 struct pthread *self = THREAD_SELF;
166 if ((self->flags & (ATTR_FLAG_POLICY_SET | ATTR_FLAG_SCHED_SET))
167 == (ATTR_FLAG_POLICY_SET | ATTR_FLAG_SCHED_SET))
168 return self->schedparam.sched_priority;
169
170 int result = 0;
171
172 /* See CREATE THREAD NOTES in nptl/pthread_create.c. */
173 lll_lock (self->lock, LLL_PRIVATE);
174
175 if ((self->flags & ATTR_FLAG_SCHED_SET) == 0)
176 {
177 if (__sched_getparam (self->tid, &self->schedparam) != 0)
178 result = -1;
179 else
180 self->flags |= ATTR_FLAG_SCHED_SET;
181 }
182
183 if ((self->flags & ATTR_FLAG_POLICY_SET) == 0)
184 {
185 self->schedpolicy = __sched_getscheduler (self->tid);
186 if (self->schedpolicy == -1)
187 result = -1;
188 else
189 self->flags |= ATTR_FLAG_POLICY_SET;
190 }
191
192 if (result != -1)
193 result = self->schedparam.sched_priority;
194
195 lll_unlock (self->lock, LLL_PRIVATE);
196
197 return result;
198}
199libc_hidden_def (__pthread_current_priority)
200