Merge 88af9b164c ("Merge tag 'acpi-6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm") into android-mainline
Steps on the way to 6.3-rc1 Change-Id: Ie3a20cd688727a3b3b9e1967f950f1dce6543e2e Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
@@ -8,7 +8,7 @@ Although RCU is usually used to protect read-mostly data structures,
|
||||
it is possible to use RCU to provide dynamic non-maskable interrupt
|
||||
handlers, as well as dynamic irq handlers. This document describes
|
||||
how to do this, drawing loosely from Zwane Mwaikambo's NMI-timer
|
||||
work in "arch/x86/kernel/traps.c".
|
||||
work in an old version of "arch/x86/kernel/traps.c".
|
||||
|
||||
The relevant pieces of code are listed below, each followed by a
|
||||
brief explanation::
|
||||
@@ -116,7 +116,7 @@ Answer to Quick Quiz:
|
||||
|
||||
This same sad story can happen on other CPUs when using
|
||||
a compiler with aggressive pointer-value speculation
|
||||
optimizations.
|
||||
optimizations. (But please don't!)
|
||||
|
||||
More important, the rcu_dereference_sched() makes it
|
||||
clear to someone reading the code that the pointer is
|
||||
|
||||
@@ -38,7 +38,7 @@ by having call_rcu() directly invoke its arguments only if it was called
|
||||
from process context. However, this can fail in a similar manner.
|
||||
|
||||
Suppose that an RCU-based algorithm again scans a linked list containing
|
||||
elements A, B, and C in process contexts, but that it invokes a function
|
||||
elements A, B, and C in process context, but that it invokes a function
|
||||
on each element as it is scanned. Suppose further that this function
|
||||
deletes element B from the list, then passes it to call_rcu() for deferred
|
||||
freeing. This may be a bit unconventional, but it is perfectly legal
|
||||
@@ -59,7 +59,8 @@ Example 3: Death by Deadlock
|
||||
Suppose that call_rcu() is invoked while holding a lock, and that the
|
||||
callback function must acquire this same lock. In this case, if
|
||||
call_rcu() were to directly invoke the callback, the result would
|
||||
be self-deadlock.
|
||||
be self-deadlock *even if* this invocation occurred from a later
|
||||
call_rcu() invocation a full grace period later.
|
||||
|
||||
In some cases, it would possible to restructure to code so that
|
||||
the call_rcu() is delayed until after the lock is released. However,
|
||||
@@ -85,6 +86,14 @@ Quick Quiz #2:
|
||||
|
||||
:ref:`Answers to Quick Quiz <answer_quick_quiz_up>`
|
||||
|
||||
It is important to note that userspace RCU implementations *do*
|
||||
permit call_rcu() to directly invoke callbacks, but only if a full
|
||||
grace period has elapsed since those callbacks were queued. This is
|
||||
the case because some userspace environments are extremely constrained.
|
||||
Nevertheless, people writing userspace RCU implementations are strongly
|
||||
encouraged to avoid invoking callbacks from call_rcu(), thus obtaining
|
||||
the deadlock-avoidance benefits called out above.
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
|
||||
@@ -69,9 +69,8 @@ checking of rcu_dereference() primitives:
|
||||
value of the pointer itself, for example, against NULL.
|
||||
|
||||
The rcu_dereference_check() check expression can be any boolean
|
||||
expression, but would normally include a lockdep expression. However,
|
||||
any boolean expression can be used. For a moderately ornate example,
|
||||
consider the following::
|
||||
expression, but would normally include a lockdep expression. For a
|
||||
moderately ornate example, consider the following::
|
||||
|
||||
file = rcu_dereference_check(fdt->fd[fd],
|
||||
lockdep_is_held(&files->file_lock) ||
|
||||
@@ -97,10 +96,10 @@ code, it could instead be written as follows::
|
||||
atomic_read(&files->count) == 1);
|
||||
|
||||
This would verify cases #2 and #3 above, and furthermore lockdep would
|
||||
complain if this was used in an RCU read-side critical section unless one
|
||||
of these two cases held. Because rcu_dereference_protected() omits all
|
||||
barriers and compiler constraints, it generates better code than do the
|
||||
other flavors of rcu_dereference(). On the other hand, it is illegal
|
||||
complain even if this was used in an RCU read-side critical section unless
|
||||
one of these two cases held. Because rcu_dereference_protected() omits
|
||||
all barriers and compiler constraints, it generates better code than do
|
||||
the other flavors of rcu_dereference(). On the other hand, it is illegal
|
||||
to use rcu_dereference_protected() if either the RCU-protected pointer
|
||||
or the RCU-protected data that it points to can change concurrently.
|
||||
|
||||
|
||||
@@ -77,15 +77,17 @@ Frequently Asked Questions
|
||||
search for the string "Patent" in Documentation/RCU/RTFP.txt to find them.
|
||||
Of these, one was allowed to lapse by the assignee, and the
|
||||
others have been contributed to the Linux kernel under GPL.
|
||||
Many (but not all) have long since expired.
|
||||
There are now also LGPL implementations of user-level RCU
|
||||
available (https://liburcu.org/).
|
||||
|
||||
- I hear that RCU needs work in order to support realtime kernels?
|
||||
|
||||
Realtime-friendly RCU can be enabled via the CONFIG_PREEMPT_RCU
|
||||
Realtime-friendly RCU are enabled via the CONFIG_PREEMPTION
|
||||
kernel configuration parameter.
|
||||
|
||||
- Where can I find more information on RCU?
|
||||
|
||||
See the Documentation/RCU/RTFP.txt file.
|
||||
Or point your browser at (http://www.rdrop.com/users/paulmck/RCU/).
|
||||
Or point your browser at (https://docs.google.com/document/d/1X0lThx8OK0ZgLMqVoXiR4ZrGURHrXK6NyLRbeXe3Xac/edit)
|
||||
or (https://docs.google.com/document/d/1GCdQC8SDbb54W1shjEXqGZ0Rq8a6kIeYutdSIajfpLA/edit?usp=sharing).
|
||||
|
||||
@@ -19,8 +19,9 @@ Follow these rules to keep your RCU code working properly:
|
||||
can reload the value, and won't your code have fun with two
|
||||
different values for a single pointer! Without rcu_dereference(),
|
||||
DEC Alpha can load a pointer, dereference that pointer, and
|
||||
return data preceding initialization that preceded the store of
|
||||
the pointer.
|
||||
return data preceding initialization that preceded the store
|
||||
of the pointer. (As noted later, in recent kernels READ_ONCE()
|
||||
also prevents DEC Alpha from playing these tricks.)
|
||||
|
||||
In addition, the volatile cast in rcu_dereference() prevents the
|
||||
compiler from deducing the resulting pointer value. Please see
|
||||
@@ -34,7 +35,7 @@ Follow these rules to keep your RCU code working properly:
|
||||
takes on the role of the lockless_dereference() primitive that
|
||||
was removed in v4.15.
|
||||
|
||||
- You are only permitted to use rcu_dereference on pointer values.
|
||||
- You are only permitted to use rcu_dereference() on pointer values.
|
||||
The compiler simply knows too much about integral values to
|
||||
trust it to carry dependencies through integer operations.
|
||||
There are a very few exceptions, namely that you can temporarily
|
||||
@@ -240,6 +241,7 @@ precautions. To see this, consider the following code fragment::
|
||||
struct foo *q;
|
||||
int r1, r2;
|
||||
|
||||
rcu_read_lock();
|
||||
p = rcu_dereference(gp2);
|
||||
if (p == NULL)
|
||||
return;
|
||||
@@ -248,7 +250,10 @@ precautions. To see this, consider the following code fragment::
|
||||
if (p == q) {
|
||||
/* The compiler decides that q->c is same as p->c. */
|
||||
r2 = p->c; /* Could get 44 on weakly order system. */
|
||||
} else {
|
||||
r2 = p->c - r1; /* Unconditional access to p->c. */
|
||||
}
|
||||
rcu_read_unlock();
|
||||
do_something_with(r1, r2);
|
||||
}
|
||||
|
||||
@@ -297,6 +302,7 @@ Then one approach is to use locking, for example, as follows::
|
||||
struct foo *q;
|
||||
int r1, r2;
|
||||
|
||||
rcu_read_lock();
|
||||
p = rcu_dereference(gp2);
|
||||
if (p == NULL)
|
||||
return;
|
||||
@@ -306,7 +312,12 @@ Then one approach is to use locking, for example, as follows::
|
||||
if (p == q) {
|
||||
/* The compiler decides that q->c is same as p->c. */
|
||||
r2 = p->c; /* Locking guarantees r2 == 144. */
|
||||
} else {
|
||||
spin_lock(&q->lock);
|
||||
r2 = q->c - r1;
|
||||
spin_unlock(&q->lock);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
spin_unlock(&p->lock);
|
||||
do_something_with(r1, r2);
|
||||
}
|
||||
@@ -364,7 +375,7 @@ the exact value of "p" even in the not-equals case. This allows the
|
||||
compiler to make the return values independent of the load from "gp",
|
||||
in turn destroying the ordering between this load and the loads of the
|
||||
return values. This can result in "p->b" returning pre-initialization
|
||||
garbage values.
|
||||
garbage values on weakly ordered systems.
|
||||
|
||||
In short, rcu_dereference() is *not* optional when you are going to
|
||||
dereference the resulting pointer.
|
||||
@@ -430,7 +441,7 @@ member of the rcu_dereference() to use in various situations:
|
||||
SPARSE CHECKING OF RCU-PROTECTED POINTERS
|
||||
-----------------------------------------
|
||||
|
||||
The sparse static-analysis tool checks for direct access to RCU-protected
|
||||
The sparse static-analysis tool checks for non-RCU access to RCU-protected
|
||||
pointers, which can result in "interesting" bugs due to compiler
|
||||
optimizations involving invented loads and perhaps also load tearing.
|
||||
For example, suppose someone mistakenly does something like this::
|
||||
|
||||
+187
-160
@@ -5,37 +5,12 @@ RCU and Unloadable Modules
|
||||
|
||||
[Originally published in LWN Jan. 14, 2007: http://lwn.net/Articles/217484/]
|
||||
|
||||
RCU (read-copy update) is a synchronization mechanism that can be thought
|
||||
of as a replacement for read-writer locking (among other things), but with
|
||||
very low-overhead readers that are immune to deadlock, priority inversion,
|
||||
and unbounded latency. RCU read-side critical sections are delimited
|
||||
by rcu_read_lock() and rcu_read_unlock(), which, in non-CONFIG_PREEMPTION
|
||||
kernels, generate no code whatsoever.
|
||||
|
||||
This means that RCU writers are unaware of the presence of concurrent
|
||||
readers, so that RCU updates to shared data must be undertaken quite
|
||||
carefully, leaving an old version of the data structure in place until all
|
||||
pre-existing readers have finished. These old versions are needed because
|
||||
such readers might hold a reference to them. RCU updates can therefore be
|
||||
rather expensive, and RCU is thus best suited for read-mostly situations.
|
||||
|
||||
How can an RCU writer possibly determine when all readers are finished,
|
||||
given that readers might well leave absolutely no trace of their
|
||||
presence? There is a synchronize_rcu() primitive that blocks until all
|
||||
pre-existing readers have completed. An updater wishing to delete an
|
||||
element p from a linked list might do the following, while holding an
|
||||
appropriate lock, of course::
|
||||
|
||||
list_del_rcu(p);
|
||||
synchronize_rcu();
|
||||
kfree(p);
|
||||
|
||||
But the above code cannot be used in IRQ context -- the call_rcu()
|
||||
primitive must be used instead. This primitive takes a pointer to an
|
||||
rcu_head struct placed within the RCU-protected data structure and
|
||||
another pointer to a function that may be invoked later to free that
|
||||
structure. Code to delete an element p from the linked list from IRQ
|
||||
context might then be as follows::
|
||||
RCU updaters sometimes use call_rcu() to initiate an asynchronous wait for
|
||||
a grace period to elapse. This primitive takes a pointer to an rcu_head
|
||||
struct placed within the RCU-protected data structure and another pointer
|
||||
to a function that may be invoked later to free that structure. Code to
|
||||
delete an element p from the linked list from IRQ context might then be
|
||||
as follows::
|
||||
|
||||
list_del_rcu(p);
|
||||
call_rcu(&p->rcu, p_callback);
|
||||
@@ -54,7 +29,7 @@ IRQ context. The function p_callback() might be defined as follows::
|
||||
Unloading Modules That Use call_rcu()
|
||||
-------------------------------------
|
||||
|
||||
But what if p_callback is defined in an unloadable module?
|
||||
But what if the p_callback() function is defined in an unloadable module?
|
||||
|
||||
If we unload the module while some RCU callbacks are pending,
|
||||
the CPUs executing these callbacks are going to be severely
|
||||
@@ -67,20 +42,21 @@ grace period to elapse, it does not wait for the callbacks to complete.
|
||||
|
||||
One might be tempted to try several back-to-back synchronize_rcu()
|
||||
calls, but this is still not guaranteed to work. If there is a very
|
||||
heavy RCU-callback load, then some of the callbacks might be deferred
|
||||
in order to allow other processing to proceed. Such deferral is required
|
||||
in realtime kernels in order to avoid excessive scheduling latencies.
|
||||
heavy RCU-callback load, then some of the callbacks might be deferred in
|
||||
order to allow other processing to proceed. For but one example, such
|
||||
deferral is required in realtime kernels in order to avoid excessive
|
||||
scheduling latencies.
|
||||
|
||||
|
||||
rcu_barrier()
|
||||
-------------
|
||||
|
||||
We instead need the rcu_barrier() primitive. Rather than waiting for
|
||||
a grace period to elapse, rcu_barrier() waits for all outstanding RCU
|
||||
callbacks to complete. Please note that rcu_barrier() does **not** imply
|
||||
synchronize_rcu(), in particular, if there are no RCU callbacks queued
|
||||
anywhere, rcu_barrier() is within its rights to return immediately,
|
||||
without waiting for a grace period to elapse.
|
||||
This situation can be handled by the rcu_barrier() primitive. Rather
|
||||
than waiting for a grace period to elapse, rcu_barrier() waits for all
|
||||
outstanding RCU callbacks to complete. Please note that rcu_barrier()
|
||||
does **not** imply synchronize_rcu(), in particular, if there are no RCU
|
||||
callbacks queued anywhere, rcu_barrier() is within its rights to return
|
||||
immediately, without waiting for anything, let alone a grace period.
|
||||
|
||||
Pseudo-code using rcu_barrier() is as follows:
|
||||
|
||||
@@ -89,83 +65,86 @@ Pseudo-code using rcu_barrier() is as follows:
|
||||
3. Allow the module to be unloaded.
|
||||
|
||||
There is also an srcu_barrier() function for SRCU, and you of course
|
||||
must match the flavor of rcu_barrier() with that of call_rcu(). If your
|
||||
module uses multiple flavors of call_rcu(), then it must also use multiple
|
||||
flavors of rcu_barrier() when unloading that module. For example, if
|
||||
it uses call_rcu(), call_srcu() on srcu_struct_1, and call_srcu() on
|
||||
srcu_struct_2, then the following three lines of code will be required
|
||||
when unloading::
|
||||
must match the flavor of srcu_barrier() with that of call_srcu().
|
||||
If your module uses multiple srcu_struct structures, then it must also
|
||||
use multiple invocations of srcu_barrier() when unloading that module.
|
||||
For example, if it uses call_rcu(), call_srcu() on srcu_struct_1, and
|
||||
call_srcu() on srcu_struct_2, then the following three lines of code
|
||||
will be required when unloading::
|
||||
|
||||
1 rcu_barrier();
|
||||
2 srcu_barrier(&srcu_struct_1);
|
||||
3 srcu_barrier(&srcu_struct_2);
|
||||
1 rcu_barrier();
|
||||
2 srcu_barrier(&srcu_struct_1);
|
||||
3 srcu_barrier(&srcu_struct_2);
|
||||
|
||||
The rcutorture module makes use of rcu_barrier() in its exit function
|
||||
as follows::
|
||||
If latency is of the essence, workqueues could be used to run these
|
||||
three functions concurrently.
|
||||
|
||||
1 static void
|
||||
2 rcu_torture_cleanup(void)
|
||||
3 {
|
||||
4 int i;
|
||||
5
|
||||
6 fullstop = 1;
|
||||
7 if (shuffler_task != NULL) {
|
||||
8 VERBOSE_PRINTK_STRING("Stopping rcu_torture_shuffle task");
|
||||
9 kthread_stop(shuffler_task);
|
||||
10 }
|
||||
11 shuffler_task = NULL;
|
||||
An ancient version of the rcutorture module makes use of rcu_barrier()
|
||||
in its exit function as follows::
|
||||
|
||||
1 static void
|
||||
2 rcu_torture_cleanup(void)
|
||||
3 {
|
||||
4 int i;
|
||||
5
|
||||
6 fullstop = 1;
|
||||
7 if (shuffler_task != NULL) {
|
||||
8 VERBOSE_PRINTK_STRING("Stopping rcu_torture_shuffle task");
|
||||
9 kthread_stop(shuffler_task);
|
||||
10 }
|
||||
11 shuffler_task = NULL;
|
||||
12
|
||||
13 if (writer_task != NULL) {
|
||||
14 VERBOSE_PRINTK_STRING("Stopping rcu_torture_writer task");
|
||||
15 kthread_stop(writer_task);
|
||||
16 }
|
||||
17 writer_task = NULL;
|
||||
13 if (writer_task != NULL) {
|
||||
14 VERBOSE_PRINTK_STRING("Stopping rcu_torture_writer task");
|
||||
15 kthread_stop(writer_task);
|
||||
16 }
|
||||
17 writer_task = NULL;
|
||||
18
|
||||
19 if (reader_tasks != NULL) {
|
||||
20 for (i = 0; i < nrealreaders; i++) {
|
||||
21 if (reader_tasks[i] != NULL) {
|
||||
22 VERBOSE_PRINTK_STRING(
|
||||
23 "Stopping rcu_torture_reader task");
|
||||
24 kthread_stop(reader_tasks[i]);
|
||||
25 }
|
||||
26 reader_tasks[i] = NULL;
|
||||
27 }
|
||||
28 kfree(reader_tasks);
|
||||
29 reader_tasks = NULL;
|
||||
30 }
|
||||
31 rcu_torture_current = NULL;
|
||||
19 if (reader_tasks != NULL) {
|
||||
20 for (i = 0; i < nrealreaders; i++) {
|
||||
21 if (reader_tasks[i] != NULL) {
|
||||
22 VERBOSE_PRINTK_STRING(
|
||||
23 "Stopping rcu_torture_reader task");
|
||||
24 kthread_stop(reader_tasks[i]);
|
||||
25 }
|
||||
26 reader_tasks[i] = NULL;
|
||||
27 }
|
||||
28 kfree(reader_tasks);
|
||||
29 reader_tasks = NULL;
|
||||
30 }
|
||||
31 rcu_torture_current = NULL;
|
||||
32
|
||||
33 if (fakewriter_tasks != NULL) {
|
||||
34 for (i = 0; i < nfakewriters; i++) {
|
||||
35 if (fakewriter_tasks[i] != NULL) {
|
||||
36 VERBOSE_PRINTK_STRING(
|
||||
37 "Stopping rcu_torture_fakewriter task");
|
||||
38 kthread_stop(fakewriter_tasks[i]);
|
||||
39 }
|
||||
40 fakewriter_tasks[i] = NULL;
|
||||
41 }
|
||||
42 kfree(fakewriter_tasks);
|
||||
43 fakewriter_tasks = NULL;
|
||||
44 }
|
||||
33 if (fakewriter_tasks != NULL) {
|
||||
34 for (i = 0; i < nfakewriters; i++) {
|
||||
35 if (fakewriter_tasks[i] != NULL) {
|
||||
36 VERBOSE_PRINTK_STRING(
|
||||
37 "Stopping rcu_torture_fakewriter task");
|
||||
38 kthread_stop(fakewriter_tasks[i]);
|
||||
39 }
|
||||
40 fakewriter_tasks[i] = NULL;
|
||||
41 }
|
||||
42 kfree(fakewriter_tasks);
|
||||
43 fakewriter_tasks = NULL;
|
||||
44 }
|
||||
45
|
||||
46 if (stats_task != NULL) {
|
||||
47 VERBOSE_PRINTK_STRING("Stopping rcu_torture_stats task");
|
||||
48 kthread_stop(stats_task);
|
||||
49 }
|
||||
50 stats_task = NULL;
|
||||
46 if (stats_task != NULL) {
|
||||
47 VERBOSE_PRINTK_STRING("Stopping rcu_torture_stats task");
|
||||
48 kthread_stop(stats_task);
|
||||
49 }
|
||||
50 stats_task = NULL;
|
||||
51
|
||||
52 /* Wait for all RCU callbacks to fire. */
|
||||
53 rcu_barrier();
|
||||
52 /* Wait for all RCU callbacks to fire. */
|
||||
53 rcu_barrier();
|
||||
54
|
||||
55 rcu_torture_stats_print(); /* -After- the stats thread is stopped! */
|
||||
55 rcu_torture_stats_print(); /* -After- the stats thread is stopped! */
|
||||
56
|
||||
57 if (cur_ops->cleanup != NULL)
|
||||
58 cur_ops->cleanup();
|
||||
59 if (atomic_read(&n_rcu_torture_error))
|
||||
60 rcu_torture_print_module_parms("End of test: FAILURE");
|
||||
61 else
|
||||
62 rcu_torture_print_module_parms("End of test: SUCCESS");
|
||||
63 }
|
||||
57 if (cur_ops->cleanup != NULL)
|
||||
58 cur_ops->cleanup();
|
||||
59 if (atomic_read(&n_rcu_torture_error))
|
||||
60 rcu_torture_print_module_parms("End of test: FAILURE");
|
||||
61 else
|
||||
62 rcu_torture_print_module_parms("End of test: SUCCESS");
|
||||
63 }
|
||||
|
||||
Line 6 sets a global variable that prevents any RCU callbacks from
|
||||
re-posting themselves. This will not be necessary in most cases, since
|
||||
@@ -190,16 +169,17 @@ Quick Quiz #1:
|
||||
:ref:`Answer to Quick Quiz #1 <answer_rcubarrier_quiz_1>`
|
||||
|
||||
Your module might have additional complications. For example, if your
|
||||
module invokes call_rcu() from timers, you will need to first cancel all
|
||||
the timers, and only then invoke rcu_barrier() to wait for any remaining
|
||||
module invokes call_rcu() from timers, you will need to first refrain
|
||||
from posting new timers, cancel (or wait for) all the already-posted
|
||||
timers, and only then invoke rcu_barrier() to wait for any remaining
|
||||
RCU callbacks to complete.
|
||||
|
||||
Of course, if you module uses call_rcu(), you will need to invoke
|
||||
Of course, if your module uses call_rcu(), you will need to invoke
|
||||
rcu_barrier() before unloading. Similarly, if your module uses
|
||||
call_srcu(), you will need to invoke srcu_barrier() before unloading,
|
||||
and on the same srcu_struct structure. If your module uses call_rcu()
|
||||
**and** call_srcu(), then you will need to invoke rcu_barrier() **and**
|
||||
srcu_barrier().
|
||||
**and** call_srcu(), then (as noted above) you will need to invoke
|
||||
rcu_barrier() **and** srcu_barrier().
|
||||
|
||||
|
||||
Implementing rcu_barrier()
|
||||
@@ -211,27 +191,40 @@ queues. His implementation queues an RCU callback on each of the per-CPU
|
||||
callback queues, and then waits until they have all started executing, at
|
||||
which point, all earlier RCU callbacks are guaranteed to have completed.
|
||||
|
||||
The original code for rcu_barrier() was as follows::
|
||||
The original code for rcu_barrier() was roughly as follows::
|
||||
|
||||
1 void rcu_barrier(void)
|
||||
2 {
|
||||
3 BUG_ON(in_interrupt());
|
||||
4 /* Take cpucontrol mutex to protect against CPU hotplug */
|
||||
5 mutex_lock(&rcu_barrier_mutex);
|
||||
6 init_completion(&rcu_barrier_completion);
|
||||
7 atomic_set(&rcu_barrier_cpu_count, 0);
|
||||
8 on_each_cpu(rcu_barrier_func, NULL, 0, 1);
|
||||
9 wait_for_completion(&rcu_barrier_completion);
|
||||
10 mutex_unlock(&rcu_barrier_mutex);
|
||||
11 }
|
||||
1 void rcu_barrier(void)
|
||||
2 {
|
||||
3 BUG_ON(in_interrupt());
|
||||
4 /* Take cpucontrol mutex to protect against CPU hotplug */
|
||||
5 mutex_lock(&rcu_barrier_mutex);
|
||||
6 init_completion(&rcu_barrier_completion);
|
||||
7 atomic_set(&rcu_barrier_cpu_count, 1);
|
||||
8 on_each_cpu(rcu_barrier_func, NULL, 0, 1);
|
||||
9 if (atomic_dec_and_test(&rcu_barrier_cpu_count))
|
||||
10 complete(&rcu_barrier_completion);
|
||||
11 wait_for_completion(&rcu_barrier_completion);
|
||||
12 mutex_unlock(&rcu_barrier_mutex);
|
||||
13 }
|
||||
|
||||
Line 3 verifies that the caller is in process context, and lines 5 and 10
|
||||
Line 3 verifies that the caller is in process context, and lines 5 and 12
|
||||
use rcu_barrier_mutex to ensure that only one rcu_barrier() is using the
|
||||
global completion and counters at a time, which are initialized on lines
|
||||
6 and 7. Line 8 causes each CPU to invoke rcu_barrier_func(), which is
|
||||
shown below. Note that the final "1" in on_each_cpu()'s argument list
|
||||
ensures that all the calls to rcu_barrier_func() will have completed
|
||||
before on_each_cpu() returns. Line 9 then waits for the completion.
|
||||
before on_each_cpu() returns. Line 9 removes the initial count from
|
||||
rcu_barrier_cpu_count, and if this count is now zero, line 10 finalizes
|
||||
the completion, which prevents line 11 from blocking. Either way,
|
||||
line 11 then waits (if needed) for the completion.
|
||||
|
||||
.. _rcubarrier_quiz_2:
|
||||
|
||||
Quick Quiz #2:
|
||||
Why doesn't line 8 initialize rcu_barrier_cpu_count to zero,
|
||||
thereby avoiding the need for lines 9 and 10?
|
||||
|
||||
:ref:`Answer to Quick Quiz #2 <answer_rcubarrier_quiz_2>`
|
||||
|
||||
This code was rewritten in 2008 and several times thereafter, but this
|
||||
still gives the general idea.
|
||||
@@ -239,21 +232,21 @@ still gives the general idea.
|
||||
The rcu_barrier_func() runs on each CPU, where it invokes call_rcu()
|
||||
to post an RCU callback, as follows::
|
||||
|
||||
1 static void rcu_barrier_func(void *notused)
|
||||
2 {
|
||||
3 int cpu = smp_processor_id();
|
||||
4 struct rcu_data *rdp = &per_cpu(rcu_data, cpu);
|
||||
5 struct rcu_head *head;
|
||||
6
|
||||
7 head = &rdp->barrier;
|
||||
8 atomic_inc(&rcu_barrier_cpu_count);
|
||||
9 call_rcu(head, rcu_barrier_callback);
|
||||
10 }
|
||||
1 static void rcu_barrier_func(void *notused)
|
||||
2 {
|
||||
3 int cpu = smp_processor_id();
|
||||
4 struct rcu_data *rdp = &per_cpu(rcu_data, cpu);
|
||||
5 struct rcu_head *head;
|
||||
6
|
||||
7 head = &rdp->barrier;
|
||||
8 atomic_inc(&rcu_barrier_cpu_count);
|
||||
9 call_rcu(head, rcu_barrier_callback);
|
||||
10 }
|
||||
|
||||
Lines 3 and 4 locate RCU's internal per-CPU rcu_data structure,
|
||||
which contains the struct rcu_head that needed for the later call to
|
||||
call_rcu(). Line 7 picks up a pointer to this struct rcu_head, and line
|
||||
8 increments a global counter. This counter will later be decremented
|
||||
8 increments the global counter. This counter will later be decremented
|
||||
by the callback. Line 9 then registers the rcu_barrier_callback() on
|
||||
the current CPU's queue.
|
||||
|
||||
@@ -261,33 +254,34 @@ The rcu_barrier_callback() function simply atomically decrements the
|
||||
rcu_barrier_cpu_count variable and finalizes the completion when it
|
||||
reaches zero, as follows::
|
||||
|
||||
1 static void rcu_barrier_callback(struct rcu_head *notused)
|
||||
2 {
|
||||
3 if (atomic_dec_and_test(&rcu_barrier_cpu_count))
|
||||
4 complete(&rcu_barrier_completion);
|
||||
5 }
|
||||
1 static void rcu_barrier_callback(struct rcu_head *notused)
|
||||
2 {
|
||||
3 if (atomic_dec_and_test(&rcu_barrier_cpu_count))
|
||||
4 complete(&rcu_barrier_completion);
|
||||
5 }
|
||||
|
||||
.. _rcubarrier_quiz_2:
|
||||
.. _rcubarrier_quiz_3:
|
||||
|
||||
Quick Quiz #2:
|
||||
Quick Quiz #3:
|
||||
What happens if CPU 0's rcu_barrier_func() executes
|
||||
immediately (thus incrementing rcu_barrier_cpu_count to the
|
||||
value one), but the other CPU's rcu_barrier_func() invocations
|
||||
are delayed for a full grace period? Couldn't this result in
|
||||
rcu_barrier() returning prematurely?
|
||||
|
||||
:ref:`Answer to Quick Quiz #2 <answer_rcubarrier_quiz_2>`
|
||||
:ref:`Answer to Quick Quiz #3 <answer_rcubarrier_quiz_3>`
|
||||
|
||||
The current rcu_barrier() implementation is more complex, due to the need
|
||||
to avoid disturbing idle CPUs (especially on battery-powered systems)
|
||||
and the need to minimally disturb non-idle CPUs in real-time systems.
|
||||
However, the code above illustrates the concepts.
|
||||
In addition, a great many optimizations have been applied. However,
|
||||
the code above illustrates the concepts.
|
||||
|
||||
|
||||
rcu_barrier() Summary
|
||||
---------------------
|
||||
|
||||
The rcu_barrier() primitive has seen relatively little use, since most
|
||||
The rcu_barrier() primitive is used relatively infrequently, since most
|
||||
code using RCU is in the core kernel rather than in modules. However, if
|
||||
you are using RCU from an unloadable module, you need to use rcu_barrier()
|
||||
so that your module may be safely unloaded.
|
||||
@@ -302,7 +296,8 @@ Quick Quiz #1:
|
||||
Is there any other situation where rcu_barrier() might
|
||||
be required?
|
||||
|
||||
Answer: Interestingly enough, rcu_barrier() was not originally
|
||||
Answer:
|
||||
Interestingly enough, rcu_barrier() was not originally
|
||||
implemented for module unloading. Nikita Danilov was using
|
||||
RCU in a filesystem, which resulted in a similar situation at
|
||||
filesystem-unmount time. Dipankar Sarma coded up rcu_barrier()
|
||||
@@ -318,13 +313,48 @@ Answer: Interestingly enough, rcu_barrier() was not originally
|
||||
.. _answer_rcubarrier_quiz_2:
|
||||
|
||||
Quick Quiz #2:
|
||||
Why doesn't line 8 initialize rcu_barrier_cpu_count to zero,
|
||||
thereby avoiding the need for lines 9 and 10?
|
||||
|
||||
Answer:
|
||||
Suppose that the on_each_cpu() function shown on line 8 was
|
||||
delayed, so that CPU 0's rcu_barrier_func() executed and
|
||||
the corresponding grace period elapsed, all before CPU 1's
|
||||
rcu_barrier_func() started executing. This would result in
|
||||
rcu_barrier_cpu_count being decremented to zero, so that line
|
||||
11's wait_for_completion() would return immediately, failing to
|
||||
wait for CPU 1's callbacks to be invoked.
|
||||
|
||||
Note that this was not a problem when the rcu_barrier() code
|
||||
was first added back in 2005. This is because on_each_cpu()
|
||||
disables preemption, which acted as an RCU read-side critical
|
||||
section, thus preventing CPU 0's grace period from completing
|
||||
until on_each_cpu() had dealt with all of the CPUs. However,
|
||||
with the advent of preemptible RCU, rcu_barrier() no longer
|
||||
waited on nonpreemptible regions of code in preemptible kernels,
|
||||
that being the job of the new rcu_barrier_sched() function.
|
||||
|
||||
However, with the RCU flavor consolidation around v4.20, this
|
||||
possibility was once again ruled out, because the consolidated
|
||||
RCU once again waits on nonpreemptible regions of code.
|
||||
|
||||
Nevertheless, that extra count might still be a good idea.
|
||||
Relying on these sort of accidents of implementation can result
|
||||
in later surprise bugs when the implementation changes.
|
||||
|
||||
:ref:`Back to Quick Quiz #2 <rcubarrier_quiz_2>`
|
||||
|
||||
.. _answer_rcubarrier_quiz_3:
|
||||
|
||||
Quick Quiz #3:
|
||||
What happens if CPU 0's rcu_barrier_func() executes
|
||||
immediately (thus incrementing rcu_barrier_cpu_count to the
|
||||
value one), but the other CPU's rcu_barrier_func() invocations
|
||||
are delayed for a full grace period? Couldn't this result in
|
||||
rcu_barrier() returning prematurely?
|
||||
|
||||
Answer: This cannot happen. The reason is that on_each_cpu() has its last
|
||||
Answer:
|
||||
This cannot happen. The reason is that on_each_cpu() has its last
|
||||
argument, the wait flag, set to "1". This flag is passed through
|
||||
to smp_call_function() and further to smp_call_function_on_cpu(),
|
||||
causing this latter to spin until the cross-CPU invocation of
|
||||
@@ -336,18 +366,15 @@ Answer: This cannot happen. The reason is that on_each_cpu() has its last
|
||||
|
||||
Therefore, on_each_cpu() disables preemption across its call
|
||||
to smp_call_function() and also across the local call to
|
||||
rcu_barrier_func(). This prevents the local CPU from context
|
||||
switching, again preventing grace periods from completing. This
|
||||
rcu_barrier_func(). Because recent RCU implementations treat
|
||||
preemption-disabled regions of code as RCU read-side critical
|
||||
sections, this prevents grace periods from completing. This
|
||||
means that all CPUs have executed rcu_barrier_func() before
|
||||
the first rcu_barrier_callback() can possibly execute, in turn
|
||||
preventing rcu_barrier_cpu_count from prematurely reaching zero.
|
||||
|
||||
Currently, -rt implementations of RCU keep but a single global
|
||||
queue for RCU callbacks, and thus do not suffer from this
|
||||
problem. However, when the -rt RCU eventually does have per-CPU
|
||||
callback queues, things will have to change. One simple change
|
||||
is to add an rcu_read_lock() before line 8 of rcu_barrier()
|
||||
and an rcu_read_unlock() after line 8 of this same function. If
|
||||
you can think of a better change, please let me know!
|
||||
But if on_each_cpu() ever decides to forgo disabling preemption,
|
||||
as might well happen due to real-time latency considerations,
|
||||
initializing rcu_barrier_cpu_count to one will save the day.
|
||||
|
||||
:ref:`Back to Quick Quiz #2 <rcubarrier_quiz_2>`
|
||||
:ref:`Back to Quick Quiz #3 <rcubarrier_quiz_3>`
|
||||
|
||||
@@ -14,19 +14,19 @@ Using 'nulls'
|
||||
=============
|
||||
|
||||
Using special makers (called 'nulls') is a convenient way
|
||||
to solve following problem :
|
||||
to solve following problem.
|
||||
|
||||
A typical RCU linked list managing objects which are
|
||||
allocated with SLAB_TYPESAFE_BY_RCU kmem_cache can
|
||||
use following algos :
|
||||
Without 'nulls', a typical RCU linked list managing objects which are
|
||||
allocated with SLAB_TYPESAFE_BY_RCU kmem_cache can use the following
|
||||
algorithms:
|
||||
|
||||
1) Lookup algo
|
||||
--------------
|
||||
1) Lookup algorithm
|
||||
-------------------
|
||||
|
||||
::
|
||||
|
||||
rcu_read_lock()
|
||||
begin:
|
||||
rcu_read_lock()
|
||||
obj = lockless_lookup(key);
|
||||
if (obj) {
|
||||
if (!try_get_ref(obj)) // might fail for free objects
|
||||
@@ -38,6 +38,7 @@ use following algos :
|
||||
*/
|
||||
if (obj->key != key) { // not the object we expected
|
||||
put_ref(obj);
|
||||
rcu_read_unlock();
|
||||
goto begin;
|
||||
}
|
||||
}
|
||||
@@ -52,9 +53,9 @@ but a version with an additional memory barrier (smp_rmb())
|
||||
{
|
||||
struct hlist_node *node, *next;
|
||||
for (pos = rcu_dereference((head)->first);
|
||||
pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) &&
|
||||
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; });
|
||||
pos = rcu_dereference(next))
|
||||
pos && ({ next = pos->next; smp_rmb(); prefetch(next); 1; }) &&
|
||||
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; });
|
||||
pos = rcu_dereference(next))
|
||||
if (obj->key == key)
|
||||
return obj;
|
||||
return NULL;
|
||||
@@ -64,9 +65,9 @@ And note the traditional hlist_for_each_entry_rcu() misses this smp_rmb()::
|
||||
|
||||
struct hlist_node *node;
|
||||
for (pos = rcu_dereference((head)->first);
|
||||
pos && ({ prefetch(pos->next); 1; }) &&
|
||||
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; });
|
||||
pos = rcu_dereference(pos->next))
|
||||
pos && ({ prefetch(pos->next); 1; }) &&
|
||||
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; });
|
||||
pos = rcu_dereference(pos->next))
|
||||
if (obj->key == key)
|
||||
return obj;
|
||||
return NULL;
|
||||
@@ -82,36 +83,32 @@ Quoting Corey Minyard::
|
||||
solved by pre-fetching the "next" field (with proper barriers) before
|
||||
checking the key."
|
||||
|
||||
2) Insert algo
|
||||
--------------
|
||||
2) Insertion algorithm
|
||||
----------------------
|
||||
|
||||
We need to make sure a reader cannot read the new 'obj->obj_next' value
|
||||
and previous value of 'obj->key'. Or else, an item could be deleted
|
||||
and previous value of 'obj->key'. Otherwise, an item could be deleted
|
||||
from a chain, and inserted into another chain. If new chain was empty
|
||||
before the move, 'next' pointer is NULL, and lockless reader can
|
||||
not detect it missed following items in original chain.
|
||||
before the move, 'next' pointer is NULL, and lockless reader can not
|
||||
detect the fact that it missed following items in original chain.
|
||||
|
||||
::
|
||||
|
||||
/*
|
||||
* Please note that new inserts are done at the head of list,
|
||||
* not in the middle or end.
|
||||
*/
|
||||
* Please note that new inserts are done at the head of list,
|
||||
* not in the middle or end.
|
||||
*/
|
||||
obj = kmem_cache_alloc(...);
|
||||
lock_chain(); // typically a spin_lock()
|
||||
obj->key = key;
|
||||
/*
|
||||
* we need to make sure obj->key is updated before obj->next
|
||||
* or obj->refcnt
|
||||
*/
|
||||
smp_wmb();
|
||||
atomic_set(&obj->refcnt, 1);
|
||||
atomic_set_release(&obj->refcnt, 1); // key before refcnt
|
||||
hlist_add_head_rcu(&obj->obj_node, list);
|
||||
unlock_chain(); // typically a spin_unlock()
|
||||
|
||||
|
||||
3) Remove algo
|
||||
--------------
|
||||
3) Removal algorithm
|
||||
--------------------
|
||||
|
||||
Nothing special here, we can use a standard RCU hlist deletion.
|
||||
But thanks to SLAB_TYPESAFE_BY_RCU, beware a deleted object can be reused
|
||||
very very fast (before the end of RCU grace period)
|
||||
@@ -133,7 +130,7 @@ Avoiding extra smp_rmb()
|
||||
========================
|
||||
|
||||
With hlist_nulls we can avoid extra smp_rmb() in lockless_lookup()
|
||||
and extra smp_wmb() in insert function.
|
||||
and extra _release() in insert function.
|
||||
|
||||
For example, if we choose to store the slot number as the 'nulls'
|
||||
end-of-list marker for each slot of the hash table, we can detect
|
||||
@@ -142,59 +139,61 @@ to another chain) checking the final 'nulls' value if
|
||||
the lookup met the end of chain. If final 'nulls' value
|
||||
is not the slot number, then we must restart the lookup at
|
||||
the beginning. If the object was moved to the same chain,
|
||||
then the reader doesn't care : It might eventually
|
||||
then the reader doesn't care: It might occasionally
|
||||
scan the list again without harm.
|
||||
|
||||
|
||||
1) lookup algo
|
||||
--------------
|
||||
1) lookup algorithm
|
||||
-------------------
|
||||
|
||||
::
|
||||
|
||||
head = &table[slot];
|
||||
rcu_read_lock();
|
||||
begin:
|
||||
rcu_read_lock();
|
||||
hlist_nulls_for_each_entry_rcu(obj, node, head, member) {
|
||||
if (obj->key == key) {
|
||||
if (!try_get_ref(obj)) // might fail for free objects
|
||||
goto begin;
|
||||
if (obj->key != key) { // not the object we expected
|
||||
put_ref(obj);
|
||||
if (!try_get_ref(obj)) { // might fail for free objects
|
||||
rcu_read_unlock();
|
||||
goto begin;
|
||||
}
|
||||
goto out;
|
||||
if (obj->key != key) { // not the object we expected
|
||||
put_ref(obj);
|
||||
rcu_read_unlock();
|
||||
goto begin;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
// If the nulls value we got at the end of this lookup is
|
||||
// not the expected one, we must restart lookup.
|
||||
// We probably met an item that was moved to another chain.
|
||||
if (get_nulls_value(node) != slot) {
|
||||
put_ref(obj);
|
||||
rcu_read_unlock();
|
||||
goto begin;
|
||||
}
|
||||
/*
|
||||
* if the nulls value we got at the end of this lookup is
|
||||
* not the expected one, we must restart lookup.
|
||||
* We probably met an item that was moved to another chain.
|
||||
*/
|
||||
if (get_nulls_value(node) != slot)
|
||||
goto begin;
|
||||
obj = NULL;
|
||||
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
|
||||
2) Insert function
|
||||
------------------
|
||||
2) Insert algorithm
|
||||
-------------------
|
||||
|
||||
::
|
||||
|
||||
/*
|
||||
* Please note that new inserts are done at the head of list,
|
||||
* not in the middle or end.
|
||||
*/
|
||||
* Please note that new inserts are done at the head of list,
|
||||
* not in the middle or end.
|
||||
*/
|
||||
obj = kmem_cache_alloc(cachep);
|
||||
lock_chain(); // typically a spin_lock()
|
||||
obj->key = key;
|
||||
atomic_set_release(&obj->refcnt, 1); // key before refcnt
|
||||
/*
|
||||
* changes to obj->key must be visible before refcnt one
|
||||
*/
|
||||
smp_wmb();
|
||||
atomic_set(&obj->refcnt, 1);
|
||||
/*
|
||||
* insert obj in RCU way (readers might be traversing chain)
|
||||
*/
|
||||
* insert obj in RCU way (readers might be traversing chain)
|
||||
*/
|
||||
hlist_nulls_add_head_rcu(&obj->obj_node, list);
|
||||
unlock_chain(); // typically a spin_unlock()
|
||||
|
||||
+117
-18
@@ -25,10 +25,10 @@ warnings:
|
||||
|
||||
- A CPU looping with bottom halves disabled.
|
||||
|
||||
- For !CONFIG_PREEMPTION kernels, a CPU looping anywhere in the kernel
|
||||
without invoking schedule(). If the looping in the kernel is
|
||||
really expected and desirable behavior, you might need to add
|
||||
some calls to cond_resched().
|
||||
- For !CONFIG_PREEMPTION kernels, a CPU looping anywhere in the
|
||||
kernel without potentially invoking schedule(). If the looping
|
||||
in the kernel is really expected and desirable behavior, you
|
||||
might need to add some calls to cond_resched().
|
||||
|
||||
- Booting Linux using a console connection that is too slow to
|
||||
keep up with the boot-time console-message rate. For example,
|
||||
@@ -108,16 +108,17 @@ warnings:
|
||||
|
||||
- A bug in the RCU implementation.
|
||||
|
||||
- A hardware failure. This is quite unlikely, but has occurred
|
||||
at least once in real life. A CPU failed in a running system,
|
||||
becoming unresponsive, but not causing an immediate crash.
|
||||
This resulted in a series of RCU CPU stall warnings, eventually
|
||||
leading the realization that the CPU had failed.
|
||||
- A hardware failure. This is quite unlikely, but is not at all
|
||||
uncommon in large datacenter. In one memorable case some decades
|
||||
back, a CPU failed in a running system, becoming unresponsive,
|
||||
but not causing an immediate crash. This resulted in a series
|
||||
of RCU CPU stall warnings, eventually leading the realization
|
||||
that the CPU had failed.
|
||||
|
||||
The RCU, RCU-sched, and RCU-tasks implementations have CPU stall warning.
|
||||
Note that SRCU does *not* have CPU stall warnings. Please note that
|
||||
RCU only detects CPU stalls when there is a grace period in progress.
|
||||
No grace period, no CPU stall warnings.
|
||||
The RCU, RCU-sched, RCU-tasks, and RCU-tasks-trace implementations have
|
||||
CPU stall warning. Note that SRCU does *not* have CPU stall warnings.
|
||||
Please note that RCU only detects CPU stalls when there is a grace period
|
||||
in progress. No grace period, no CPU stall warnings.
|
||||
|
||||
To diagnose the cause of the stall, inspect the stack traces.
|
||||
The offending function will usually be near the top of the stack.
|
||||
@@ -205,16 +206,21 @@ RCU_STALL_RAT_DELAY
|
||||
rcupdate.rcu_task_stall_timeout
|
||||
-------------------------------
|
||||
|
||||
This boot/sysfs parameter controls the RCU-tasks stall warning
|
||||
interval. A value of zero or less suppresses RCU-tasks stall
|
||||
warnings. A positive value sets the stall-warning interval
|
||||
in seconds. An RCU-tasks stall warning starts with the line:
|
||||
This boot/sysfs parameter controls the RCU-tasks and
|
||||
RCU-tasks-trace stall warning intervals. A value of zero or less
|
||||
suppresses RCU-tasks stall warnings. A positive value sets the
|
||||
stall-warning interval in seconds. An RCU-tasks stall warning
|
||||
starts with the line:
|
||||
|
||||
INFO: rcu_tasks detected stalls on tasks:
|
||||
|
||||
And continues with the output of sched_show_task() for each
|
||||
task stalling the current RCU-tasks grace period.
|
||||
|
||||
An RCU-tasks-trace stall warning starts (and continues) similarly:
|
||||
|
||||
INFO: rcu_tasks_trace detected stalls on tasks
|
||||
|
||||
|
||||
Interpreting RCU's CPU Stall-Detector "Splats"
|
||||
==============================================
|
||||
@@ -248,7 +254,8 @@ dynticks counter, which will have an even-numbered value if the CPU
|
||||
is in dyntick-idle mode and an odd-numbered value otherwise. The hex
|
||||
number between the two "/"s is the value of the nesting, which will be
|
||||
a small non-negative number if in the idle loop (as shown above) and a
|
||||
very large positive number otherwise.
|
||||
very large positive number otherwise. The number following the final
|
||||
"/" is the NMI nesting, which will be a small non-negative number.
|
||||
|
||||
The "softirq=" portion of the message tracks the number of RCU softirq
|
||||
handlers that the stalled CPU has executed. The number before the "/"
|
||||
@@ -383,3 +390,95 @@ for example, "P3421".
|
||||
|
||||
It is entirely possible to see stall warnings from normal and from
|
||||
expedited grace periods at about the same time during the same run.
|
||||
|
||||
RCU_CPU_STALL_CPUTIME
|
||||
=====================
|
||||
|
||||
In kernels built with CONFIG_RCU_CPU_STALL_CPUTIME=y or booted with
|
||||
rcupdate.rcu_cpu_stall_cputime=1, the following additional information
|
||||
is supplied with each RCU CPU stall warning::
|
||||
|
||||
rcu: hardirqs softirqs csw/system
|
||||
rcu: number: 624 45 0
|
||||
rcu: cputime: 69 1 2425 ==> 2500(ms)
|
||||
|
||||
These statistics are collected during the sampling period. The values
|
||||
in row "number:" are the number of hard interrupts, number of soft
|
||||
interrupts, and number of context switches on the stalled CPU. The
|
||||
first three values in row "cputime:" indicate the CPU time in
|
||||
milliseconds consumed by hard interrupts, soft interrupts, and tasks
|
||||
on the stalled CPU. The last number is the measurement interval, again
|
||||
in milliseconds. Because user-mode tasks normally do not cause RCU CPU
|
||||
stalls, these tasks are typically kernel tasks, which is why only the
|
||||
system CPU time are considered.
|
||||
|
||||
The sampling period is shown as follows::
|
||||
|
||||
|<------------first timeout---------->|<-----second timeout----->|
|
||||
|<--half timeout-->|<--half timeout-->| |
|
||||
| |<--first period-->| |
|
||||
| |<-----------second sampling period---------->|
|
||||
| | | |
|
||||
snapshot time point 1st-stall 2nd-stall
|
||||
|
||||
The following describes four typical scenarios:
|
||||
|
||||
1. A CPU looping with interrupts disabled.
|
||||
|
||||
::
|
||||
|
||||
rcu: hardirqs softirqs csw/system
|
||||
rcu: number: 0 0 0
|
||||
rcu: cputime: 0 0 0 ==> 2500(ms)
|
||||
|
||||
Because interrupts have been disabled throughout the measurement
|
||||
interval, there are no interrupts and no context switches.
|
||||
Furthermore, because CPU time consumption was measured using interrupt
|
||||
handlers, the system CPU consumption is misleadingly measured as zero.
|
||||
This scenario will normally also have "(0 ticks this GP)" printed on
|
||||
this CPU's summary line.
|
||||
|
||||
2. A CPU looping with bottom halves disabled.
|
||||
|
||||
This is similar to the previous example, but with non-zero number of
|
||||
and CPU time consumed by hard interrupts, along with non-zero CPU
|
||||
time consumed by in-kernel execution::
|
||||
|
||||
rcu: hardirqs softirqs csw/system
|
||||
rcu: number: 624 0 0
|
||||
rcu: cputime: 49 0 2446 ==> 2500(ms)
|
||||
|
||||
The fact that there are zero softirqs gives a hint that these were
|
||||
disabled, perhaps via local_bh_disable(). It is of course possible
|
||||
that there were no softirqs, perhaps because all events that would
|
||||
result in softirq execution are confined to other CPUs. In this case,
|
||||
the diagnosis should continue as shown in the next example.
|
||||
|
||||
3. A CPU looping with preemption disabled.
|
||||
|
||||
Here, only the number of context switches is zero::
|
||||
|
||||
rcu: hardirqs softirqs csw/system
|
||||
rcu: number: 624 45 0
|
||||
rcu: cputime: 69 1 2425 ==> 2500(ms)
|
||||
|
||||
This situation hints that the stalled CPU was looping with preemption
|
||||
disabled.
|
||||
|
||||
4. No looping, but massive hard and soft interrupts.
|
||||
|
||||
::
|
||||
|
||||
rcu: hardirqs softirqs csw/system
|
||||
rcu: number: xx xx 0
|
||||
rcu: cputime: xx xx 0 ==> 2500(ms)
|
||||
|
||||
Here, the number and CPU time of hard interrupts are all non-zero,
|
||||
but the number of context switches and the in-kernel CPU time consumed
|
||||
are zero. The number and cputime of soft interrupts will usually be
|
||||
non-zero, but could be zero, for example, if the CPU was spinning
|
||||
within a single hard interrupt handler.
|
||||
|
||||
If this type of RCU CPU stall warning can be reproduced, you can
|
||||
narrow it down by looking at /proc/interrupts or by writing code to
|
||||
trace each interrupt, for example, by referring to show_interrupts().
|
||||
|
||||
@@ -206,7 +206,11 @@ values for memory may require disabling the callback-flooding tests
|
||||
using the --bootargs parameter discussed below.
|
||||
|
||||
Sometimes additional debugging is useful, and in such cases the --kconfig
|
||||
parameter to kvm.sh may be used, for example, ``--kconfig 'CONFIG_KASAN=y'``.
|
||||
parameter to kvm.sh may be used, for example, ``--kconfig 'CONFIG_RCU_EQS_DEBUG=y'``.
|
||||
In addition, there are the --gdb, --kasan, and --kcsan parameters.
|
||||
Note that --gdb limits you to one scenario per kvm.sh run and requires
|
||||
that you have another window open from which to run ``gdb`` as instructed
|
||||
by the script.
|
||||
|
||||
Kernel boot arguments can also be supplied, for example, to control
|
||||
rcutorture's module parameters. For example, to test a change to RCU's
|
||||
@@ -219,10 +223,17 @@ require disabling rcutorture's callback-flooding tests::
|
||||
--bootargs 'rcutorture.fwd_progress=0'
|
||||
|
||||
Sometimes all that is needed is a full set of kernel builds. This is
|
||||
what the --buildonly argument does.
|
||||
what the --buildonly parameter does.
|
||||
|
||||
Finally, the --trust-make argument allows each kernel build to reuse what
|
||||
it can from the previous kernel build.
|
||||
The --duration parameter can override the default run time of 30 minutes.
|
||||
For example, ``--duration 2d`` would run for two days, ``--duration 3h``
|
||||
would run for three hours, ``--duration 5m`` would run for five minutes,
|
||||
and ``--duration 45s`` would run for 45 seconds. This last can be useful
|
||||
for tracking down rare boot-time failures.
|
||||
|
||||
Finally, the --trust-make parameter allows each kernel build to reuse what
|
||||
it can from the previous kernel build. Please note that without the
|
||||
--trust-make parameter, your tags files may be demolished.
|
||||
|
||||
There are additional more arcane arguments that are documented in the
|
||||
source code of the kvm.sh script.
|
||||
@@ -291,3 +302,73 @@ the following summary at the end of the run on a 12-CPU system::
|
||||
TREE07 ------- 167347 GPs (30.9902/s) [rcu: g1079021 f0x0 ] n_max_cbs: 478732
|
||||
CPU count limited from 16 to 12
|
||||
TREE09 ------- 752238 GPs (139.303/s) [rcu: g13075057 f0x0 ] n_max_cbs: 99011
|
||||
|
||||
|
||||
Repeated Runs
|
||||
=============
|
||||
|
||||
Suppose that you are chasing down a rare boot-time failure. Although you
|
||||
could use kvm.sh, doing so will rebuild the kernel on each run. If you
|
||||
need (say) 1,000 runs to have confidence that you have fixed the bug,
|
||||
these pointless rebuilds can become extremely annoying.
|
||||
|
||||
This is why kvm-again.sh exists.
|
||||
|
||||
Suppose that a previous kvm.sh run left its output in this directory::
|
||||
|
||||
tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28
|
||||
|
||||
Then this run can be re-run without rebuilding as follow:
|
||||
|
||||
kvm-again.sh tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28
|
||||
|
||||
A few of the original run's kvm.sh parameters may be overridden, perhaps
|
||||
most notably --duration and --bootargs. For example::
|
||||
|
||||
kvm-again.sh tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28 \
|
||||
--duration 45s
|
||||
|
||||
would re-run the previous test, but for only 45 seconds, thus facilitating
|
||||
tracking down the aforementioned rare boot-time failure.
|
||||
|
||||
|
||||
Distributed Runs
|
||||
================
|
||||
|
||||
Although kvm.sh is quite useful, its testing is confined to a single
|
||||
system. It is not all that hard to use your favorite framework to cause
|
||||
(say) 5 instances of kvm.sh to run on your 5 systems, but this will very
|
||||
likely unnecessarily rebuild kernels. In addition, manually distributing
|
||||
the desired rcutorture scenarios across the available systems can be
|
||||
painstaking and error-prone.
|
||||
|
||||
And this is why the kvm-remote.sh script exists.
|
||||
|
||||
If you the following command works::
|
||||
|
||||
ssh system0 date
|
||||
|
||||
and if it also works for system1, system2, system3, system4, and system5,
|
||||
and all of these systems have 64 CPUs, you can type::
|
||||
|
||||
kvm-remote.sh "system0 system1 system2 system3 system4 system5" \
|
||||
--cpus 64 --duration 8h --configs "5*CFLIST"
|
||||
|
||||
This will build each default scenario's kernel on the local system, then
|
||||
spread each of five instances of each scenario over the systems listed,
|
||||
running each scenario for eight hours. At the end of the runs, the
|
||||
results will be gathered, recorded, and printed. Most of the parameters
|
||||
that kvm.sh will accept can be passed to kvm-remote.sh, but the list of
|
||||
systems must come first.
|
||||
|
||||
The kvm.sh ``--dryrun scenarios`` argument is useful for working out
|
||||
how many scenarios may be run in one batch across a group of systems.
|
||||
|
||||
You can also re-run a previous remote run in a manner similar to kvm.sh:
|
||||
|
||||
kvm-remote.sh "system0 system1 system2 system3 system4 system5" \
|
||||
tools/testing/selftests/rcutorture/res/2022.11.03-11.26.28-remote \
|
||||
--duration 24h
|
||||
|
||||
In this case, most of the kvm-again.sh parmeters may be supplied following
|
||||
the pathname of the old run-results directory.
|
||||
|
||||
+123
-66
@@ -16,18 +16,23 @@ to start learning about RCU:
|
||||
| 6. The RCU API, 2019 Edition https://lwn.net/Articles/777036/
|
||||
| 2019 Big API Table https://lwn.net/Articles/777165/
|
||||
|
||||
For those preferring video:
|
||||
|
||||
| 1. Unraveling RCU Mysteries: Fundamentals https://www.linuxfoundation.org/webinars/unraveling-rcu-usage-mysteries
|
||||
| 2. Unraveling RCU Mysteries: Additional Use Cases https://www.linuxfoundation.org/webinars/unraveling-rcu-usage-mysteries-additional-use-cases
|
||||
|
||||
|
||||
What is RCU?
|
||||
|
||||
RCU is a synchronization mechanism that was added to the Linux kernel
|
||||
during the 2.5 development effort that is optimized for read-mostly
|
||||
situations. Although RCU is actually quite simple once you understand it,
|
||||
getting there can sometimes be a challenge. Part of the problem is that
|
||||
most of the past descriptions of RCU have been written with the mistaken
|
||||
assumption that there is "one true way" to describe RCU. Instead,
|
||||
the experience has been that different people must take different paths
|
||||
to arrive at an understanding of RCU. This document provides several
|
||||
different paths, as follows:
|
||||
situations. Although RCU is actually quite simple, making effective use
|
||||
of it requires you to think differently about your code. Another part
|
||||
of the problem is the mistaken assumption that there is "one true way" to
|
||||
describe and to use RCU. Instead, the experience has been that different
|
||||
people must take different paths to arrive at an understanding of RCU,
|
||||
depending on their experiences and use cases. This document provides
|
||||
several different paths, as follows:
|
||||
|
||||
:ref:`1. RCU OVERVIEW <1_whatisRCU>`
|
||||
|
||||
@@ -157,34 +162,36 @@ rcu_read_lock()
|
||||
^^^^^^^^^^^^^^^
|
||||
void rcu_read_lock(void);
|
||||
|
||||
Used by a reader to inform the reclaimer that the reader is
|
||||
entering an RCU read-side critical section. It is illegal
|
||||
to block while in an RCU read-side critical section, though
|
||||
kernels built with CONFIG_PREEMPT_RCU can preempt RCU
|
||||
read-side critical sections. Any RCU-protected data structure
|
||||
accessed during an RCU read-side critical section is guaranteed to
|
||||
remain unreclaimed for the full duration of that critical section.
|
||||
Reference counts may be used in conjunction with RCU to maintain
|
||||
longer-term references to data structures.
|
||||
This temporal primitive is used by a reader to inform the
|
||||
reclaimer that the reader is entering an RCU read-side critical
|
||||
section. It is illegal to block while in an RCU read-side
|
||||
critical section, though kernels built with CONFIG_PREEMPT_RCU
|
||||
can preempt RCU read-side critical sections. Any RCU-protected
|
||||
data structure accessed during an RCU read-side critical section
|
||||
is guaranteed to remain unreclaimed for the full duration of that
|
||||
critical section. Reference counts may be used in conjunction
|
||||
with RCU to maintain longer-term references to data structures.
|
||||
|
||||
rcu_read_unlock()
|
||||
^^^^^^^^^^^^^^^^^
|
||||
void rcu_read_unlock(void);
|
||||
|
||||
Used by a reader to inform the reclaimer that the reader is
|
||||
exiting an RCU read-side critical section. Note that RCU
|
||||
read-side critical sections may be nested and/or overlapping.
|
||||
This temporal primitives is used by a reader to inform the
|
||||
reclaimer that the reader is exiting an RCU read-side critical
|
||||
section. Note that RCU read-side critical sections may be nested
|
||||
and/or overlapping.
|
||||
|
||||
synchronize_rcu()
|
||||
^^^^^^^^^^^^^^^^^
|
||||
void synchronize_rcu(void);
|
||||
|
||||
Marks the end of updater code and the beginning of reclaimer
|
||||
code. It does this by blocking until all pre-existing RCU
|
||||
read-side critical sections on all CPUs have completed.
|
||||
Note that synchronize_rcu() will **not** necessarily wait for
|
||||
any subsequent RCU read-side critical sections to complete.
|
||||
For example, consider the following sequence of events::
|
||||
This temporal primitive marks the end of updater code and the
|
||||
beginning of reclaimer code. It does this by blocking until
|
||||
all pre-existing RCU read-side critical sections on all CPUs
|
||||
have completed. Note that synchronize_rcu() will **not**
|
||||
necessarily wait for any subsequent RCU read-side critical
|
||||
sections to complete. For example, consider the following
|
||||
sequence of events::
|
||||
|
||||
CPU 0 CPU 1 CPU 2
|
||||
----------------- ------------------------- ---------------
|
||||
@@ -211,13 +218,13 @@ synchronize_rcu()
|
||||
to be useful in all but the most read-intensive situations,
|
||||
synchronize_rcu()'s overhead must also be quite small.
|
||||
|
||||
The call_rcu() API is a callback form of synchronize_rcu(),
|
||||
and is described in more detail in a later section. Instead of
|
||||
blocking, it registers a function and argument which are invoked
|
||||
after all ongoing RCU read-side critical sections have completed.
|
||||
This callback variant is particularly useful in situations where
|
||||
it is illegal to block or where update-side performance is
|
||||
critically important.
|
||||
The call_rcu() API is an asynchronous callback form of
|
||||
synchronize_rcu(), and is described in more detail in a later
|
||||
section. Instead of blocking, it registers a function and
|
||||
argument which are invoked after all ongoing RCU read-side
|
||||
critical sections have completed. This callback variant is
|
||||
particularly useful in situations where it is illegal to block
|
||||
or where update-side performance is critically important.
|
||||
|
||||
However, the call_rcu() API should not be used lightly, as use
|
||||
of the synchronize_rcu() API generally results in simpler code.
|
||||
@@ -236,11 +243,13 @@ rcu_assign_pointer()
|
||||
would be cool to be able to declare a function in this manner.
|
||||
(Compiler experts will no doubt disagree.)
|
||||
|
||||
The updater uses this function to assign a new value to an
|
||||
The updater uses this spatial macro to assign a new value to an
|
||||
RCU-protected pointer, in order to safely communicate the change
|
||||
in value from the updater to the reader. This macro does not
|
||||
evaluate to an rvalue, but it does execute any memory-barrier
|
||||
instructions required for a given CPU architecture.
|
||||
in value from the updater to the reader. This is a spatial (as
|
||||
opposed to temporal) macro. It does not evaluate to an rvalue,
|
||||
but it does execute any memory-barrier instructions required
|
||||
for a given CPU architecture. Its ordering properties are that
|
||||
of a store-release operation.
|
||||
|
||||
Perhaps just as important, it serves to document (1) which
|
||||
pointers are protected by RCU and (2) the point at which a
|
||||
@@ -255,14 +264,15 @@ rcu_dereference()
|
||||
Like rcu_assign_pointer(), rcu_dereference() must be implemented
|
||||
as a macro.
|
||||
|
||||
The reader uses rcu_dereference() to fetch an RCU-protected
|
||||
pointer, which returns a value that may then be safely
|
||||
dereferenced. Note that rcu_dereference() does not actually
|
||||
dereference the pointer, instead, it protects the pointer for
|
||||
later dereferencing. It also executes any needed memory-barrier
|
||||
instructions for a given CPU architecture. Currently, only Alpha
|
||||
needs memory barriers within rcu_dereference() -- on other CPUs,
|
||||
it compiles to nothing, not even a compiler directive.
|
||||
The reader uses the spatial rcu_dereference() macro to fetch
|
||||
an RCU-protected pointer, which returns a value that may
|
||||
then be safely dereferenced. Note that rcu_dereference()
|
||||
does not actually dereference the pointer, instead, it
|
||||
protects the pointer for later dereferencing. It also
|
||||
executes any needed memory-barrier instructions for a given
|
||||
CPU architecture. Currently, only Alpha needs memory barriers
|
||||
within rcu_dereference() -- on other CPUs, it compiles to a
|
||||
volatile load.
|
||||
|
||||
Common coding practice uses rcu_dereference() to copy an
|
||||
RCU-protected pointer to a local variable, then dereferences
|
||||
@@ -355,12 +365,15 @@ reader, updater, and reclaimer.
|
||||
synchronize_rcu() & call_rcu()
|
||||
|
||||
|
||||
The RCU infrastructure observes the time sequence of rcu_read_lock(),
|
||||
The RCU infrastructure observes the temporal sequence of rcu_read_lock(),
|
||||
rcu_read_unlock(), synchronize_rcu(), and call_rcu() invocations in
|
||||
order to determine when (1) synchronize_rcu() invocations may return
|
||||
to their callers and (2) call_rcu() callbacks may be invoked. Efficient
|
||||
implementations of the RCU infrastructure make heavy use of batching in
|
||||
order to amortize their overhead over many uses of the corresponding APIs.
|
||||
The rcu_assign_pointer() and rcu_dereference() invocations communicate
|
||||
spatial changes via stores to and loads from the RCU-protected pointer in
|
||||
question.
|
||||
|
||||
There are at least three flavors of RCU usage in the Linux kernel. The diagram
|
||||
above shows the most common one. On the updater side, the rcu_assign_pointer(),
|
||||
@@ -392,7 +405,9 @@ b. RCU applied to networking data structures that may be subjected
|
||||
c. RCU applied to scheduler and interrupt/NMI-handler tasks.
|
||||
|
||||
Again, most uses will be of (a). The (b) and (c) cases are important
|
||||
for specialized uses, but are relatively uncommon.
|
||||
for specialized uses, but are relatively uncommon. The SRCU, RCU-Tasks,
|
||||
RCU-Tasks-Rude, and RCU-Tasks-Trace have similar relationships among
|
||||
their assorted primitives.
|
||||
|
||||
.. _3_whatisRCU:
|
||||
|
||||
@@ -468,7 +483,7 @@ So, to sum up:
|
||||
- Within an RCU read-side critical section, use rcu_dereference()
|
||||
to dereference RCU-protected pointers.
|
||||
|
||||
- Use some solid scheme (such as locks or semaphores) to
|
||||
- Use some solid design (such as locks or semaphores) to
|
||||
keep concurrent updates from interfering with each other.
|
||||
|
||||
- Use rcu_assign_pointer() to update an RCU-protected pointer.
|
||||
@@ -579,6 +594,14 @@ to avoid having to write your own callback::
|
||||
|
||||
kfree_rcu(old_fp, rcu);
|
||||
|
||||
If the occasional sleep is permitted, the single-argument form may
|
||||
be used, omitting the rcu_head structure from struct foo.
|
||||
|
||||
kfree_rcu(old_fp);
|
||||
|
||||
This variant of kfree_rcu() almost never blocks, but might do so by
|
||||
invoking synchronize_rcu() in response to memory-allocation failure.
|
||||
|
||||
Again, see checklist.rst for additional rules governing the use of RCU.
|
||||
|
||||
.. _5_whatisRCU:
|
||||
@@ -596,7 +619,7 @@ lacking both functionality and performance. However, they are useful
|
||||
in getting a feel for how RCU works. See kernel/rcu/update.c for a
|
||||
production-quality implementation, and see:
|
||||
|
||||
http://www.rdrop.com/users/paulmck/RCU
|
||||
https://docs.google.com/document/d/1X0lThx8OK0ZgLMqVoXiR4ZrGURHrXK6NyLRbeXe3Xac/edit
|
||||
|
||||
for papers describing the Linux kernel RCU implementation. The OLS'01
|
||||
and OLS'02 papers are a good introduction, and the dissertation provides
|
||||
@@ -929,6 +952,8 @@ unfortunately any spinlock in a ``SLAB_TYPESAFE_BY_RCU`` object must be
|
||||
initialized after each and every call to kmem_cache_alloc(), which renders
|
||||
reference-free spinlock acquisition completely unsafe. Therefore, when
|
||||
using ``SLAB_TYPESAFE_BY_RCU``, make proper use of a reference counter.
|
||||
(Those willing to use a kmem_cache constructor may also use locking,
|
||||
including cache-friendly sequence locking.)
|
||||
|
||||
With traditional reference counting -- such as that implemented by the
|
||||
kref library in Linux -- there is typically code that runs when the last
|
||||
@@ -1047,6 +1072,30 @@ sched::
|
||||
rcu_read_lock_sched_held
|
||||
|
||||
|
||||
RCU-Tasks::
|
||||
|
||||
Critical sections Grace period Barrier
|
||||
|
||||
N/A call_rcu_tasks rcu_barrier_tasks
|
||||
synchronize_rcu_tasks
|
||||
|
||||
|
||||
RCU-Tasks-Rude::
|
||||
|
||||
Critical sections Grace period Barrier
|
||||
|
||||
N/A call_rcu_tasks_rude rcu_barrier_tasks_rude
|
||||
synchronize_rcu_tasks_rude
|
||||
|
||||
|
||||
RCU-Tasks-Trace::
|
||||
|
||||
Critical sections Grace period Barrier
|
||||
|
||||
rcu_read_lock_trace call_rcu_tasks_trace rcu_barrier_tasks_trace
|
||||
rcu_read_unlock_trace synchronize_rcu_tasks_trace
|
||||
|
||||
|
||||
SRCU::
|
||||
|
||||
Critical sections Grace period Barrier
|
||||
@@ -1087,35 +1136,43 @@ list can be helpful:
|
||||
|
||||
a. Will readers need to block? If so, you need SRCU.
|
||||
|
||||
b. What about the -rt patchset? If readers would need to block
|
||||
in an non-rt kernel, you need SRCU. If readers would block
|
||||
in a -rt kernel, but not in a non-rt kernel, SRCU is not
|
||||
necessary. (The -rt patchset turns spinlocks into sleeplocks,
|
||||
hence this distinction.)
|
||||
b. Will readers need to block and are you doing tracing, for
|
||||
example, ftrace or BPF? If so, you need RCU-tasks,
|
||||
RCU-tasks-rude, and/or RCU-tasks-trace.
|
||||
|
||||
c. Do you need to treat NMI handlers, hardirq handlers,
|
||||
c. What about the -rt patchset? If readers would need to block in
|
||||
an non-rt kernel, you need SRCU. If readers would block when
|
||||
acquiring spinlocks in a -rt kernel, but not in a non-rt kernel,
|
||||
SRCU is not necessary. (The -rt patchset turns spinlocks into
|
||||
sleeplocks, hence this distinction.)
|
||||
|
||||
d. Do you need to treat NMI handlers, hardirq handlers,
|
||||
and code segments with preemption disabled (whether
|
||||
via preempt_disable(), local_irq_save(), local_bh_disable(),
|
||||
or some other mechanism) as if they were explicit RCU readers?
|
||||
If so, RCU-sched is the only choice that will work for you.
|
||||
If so, RCU-sched readers are the only choice that will work
|
||||
for you, but since about v4.20 you use can use the vanilla RCU
|
||||
update primitives.
|
||||
|
||||
d. Do you need RCU grace periods to complete even in the face
|
||||
of softirq monopolization of one or more of the CPUs? For
|
||||
example, is your code subject to network-based denial-of-service
|
||||
attacks? If so, you should disable softirq across your readers,
|
||||
for example, by using rcu_read_lock_bh().
|
||||
e. Do you need RCU grace periods to complete even in the face of
|
||||
softirq monopolization of one or more of the CPUs? For example,
|
||||
is your code subject to network-based denial-of-service attacks?
|
||||
If so, you should disable softirq across your readers, for
|
||||
example, by using rcu_read_lock_bh(). Since about v4.20 you
|
||||
use can use the vanilla RCU update primitives.
|
||||
|
||||
e. Is your workload too update-intensive for normal use of
|
||||
f. Is your workload too update-intensive for normal use of
|
||||
RCU, but inappropriate for other synchronization mechanisms?
|
||||
If so, consider SLAB_TYPESAFE_BY_RCU (which was originally
|
||||
named SLAB_DESTROY_BY_RCU). But please be careful!
|
||||
|
||||
f. Do you need read-side critical sections that are respected
|
||||
even though they are in the middle of the idle loop, during
|
||||
user-mode execution, or on an offlined CPU? If so, SRCU is the
|
||||
only choice that will work for you.
|
||||
g. Do you need read-side critical sections that are respected even
|
||||
on CPUs that are deep in the idle loop, during entry to or exit
|
||||
from user-mode execution, or on an offlined CPU? If so, SRCU
|
||||
and RCU Tasks Trace are the only choices that will work for you,
|
||||
with SRCU being strongly preferred in almost all cases.
|
||||
|
||||
g. Otherwise, use RCU.
|
||||
h. Otherwise, use RCU.
|
||||
|
||||
Of course, this all assumes that you have determined that RCU is in fact
|
||||
the right tool for your job.
|
||||
|
||||
@@ -80,6 +80,8 @@ access. For example, cpusets (see Documentation/admin-guide/cgroup-v1/cpusets.rs
|
||||
you to associate a set of CPUs and a set of memory nodes with the
|
||||
tasks in each cgroup.
|
||||
|
||||
.. _cgroups-why-needed:
|
||||
|
||||
1.2 Why are cgroups needed ?
|
||||
----------------------------
|
||||
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
Memory Resource Controller
|
||||
==========================
|
||||
|
||||
NOTE:
|
||||
.. caution::
|
||||
This document is hopelessly outdated and it asks for a complete
|
||||
rewrite. It still contains a useful information so we are keeping it
|
||||
here but make sure to check the current code if you need a deeper
|
||||
understanding.
|
||||
|
||||
NOTE:
|
||||
.. note::
|
||||
The Memory Resource Controller has generically been referred to as the
|
||||
memory controller in this document. Do not confuse memory controller
|
||||
used here with the memory controller that is used in hardware.
|
||||
|
||||
(For editors) In this document:
|
||||
.. hint::
|
||||
When we mention a cgroup (cgroupfs's directory) with memory controller,
|
||||
we call it "memory cgroup". When you see git-log and source code, you'll
|
||||
see patch's title and function names tend to use "memcg".
|
||||
@@ -23,7 +23,7 @@ Benefits and Purpose of the memory controller
|
||||
=============================================
|
||||
|
||||
The memory controller isolates the memory behaviour of a group of tasks
|
||||
from the rest of the system. The article on LWN [12] mentions some probable
|
||||
from the rest of the system. The article on LWN [12]_ mentions some probable
|
||||
uses of the memory controller. The memory controller can be used to
|
||||
|
||||
a. Isolate an application or a group of applications
|
||||
@@ -55,7 +55,8 @@ Features:
|
||||
- Root cgroup has no limit controls.
|
||||
|
||||
Kernel memory support is a work in progress, and the current version provides
|
||||
basically functionality. (See Section 2.7)
|
||||
basically functionality. (See :ref:`section 2.7
|
||||
<cgroup-v1-memory-kernel-extension>`)
|
||||
|
||||
Brief summary of control files.
|
||||
|
||||
@@ -107,16 +108,16 @@ Brief summary of control files.
|
||||
==========
|
||||
|
||||
The memory controller has a long history. A request for comments for the memory
|
||||
controller was posted by Balbir Singh [1]. At the time the RFC was posted
|
||||
controller was posted by Balbir Singh [1]_. At the time the RFC was posted
|
||||
there were several implementations for memory control. The goal of the
|
||||
RFC was to build consensus and agreement for the minimal features required
|
||||
for memory control. The first RSS controller was posted by Balbir Singh[2]
|
||||
in Feb 2007. Pavel Emelianov [3][4][5] has since posted three versions of the
|
||||
RSS controller. At OLS, at the resource management BoF, everyone suggested
|
||||
that we handle both page cache and RSS together. Another request was raised
|
||||
to allow user space handling of OOM. The current memory controller is
|
||||
for memory control. The first RSS controller was posted by Balbir Singh [2]_
|
||||
in Feb 2007. Pavel Emelianov [3]_ [4]_ [5]_ has since posted three versions
|
||||
of the RSS controller. At OLS, at the resource management BoF, everyone
|
||||
suggested that we handle both page cache and RSS together. Another request was
|
||||
raised to allow user space handling of OOM. The current memory controller is
|
||||
at version 6; it combines both mapped (RSS) and unmapped Page
|
||||
Cache Control [11].
|
||||
Cache Control [11]_.
|
||||
|
||||
2. Memory Control
|
||||
=================
|
||||
@@ -147,7 +148,8 @@ specific data structure (mem_cgroup) associated with it.
|
||||
2.2. Accounting
|
||||
---------------
|
||||
|
||||
::
|
||||
.. code-block::
|
||||
:caption: Figure 1: Hierarchy of Accounting
|
||||
|
||||
+--------------------+
|
||||
| mem_cgroup |
|
||||
@@ -167,7 +169,6 @@ specific data structure (mem_cgroup) associated with it.
|
||||
| | | |
|
||||
+---------------+ +---------------+
|
||||
|
||||
(Figure 1: Hierarchy of Accounting)
|
||||
|
||||
|
||||
Figure 1 shows the important aspects of the controller
|
||||
@@ -221,8 +222,9 @@ behind this approach is that a cgroup that aggressively uses a shared
|
||||
page will eventually get charged for it (once it is uncharged from
|
||||
the cgroup that brought it in -- this will happen on memory pressure).
|
||||
|
||||
But see section 8.2: when moving a task to another cgroup, its pages may
|
||||
be recharged to the new cgroup, if move_charge_at_immigrate has been chosen.
|
||||
But see :ref:`section 8.2 <cgroup-v1-memory-movable-charges>` when moving a
|
||||
task to another cgroup, its pages may be recharged to the new cgroup, if
|
||||
move_charge_at_immigrate has been chosen.
|
||||
|
||||
2.4 Swap Extension
|
||||
--------------------------------------
|
||||
@@ -244,7 +246,8 @@ In this case, setting memsw.limit_in_bytes=3G will prevent bad use of swap.
|
||||
By using the memsw limit, you can avoid system OOM which can be caused by swap
|
||||
shortage.
|
||||
|
||||
**why 'memory+swap' rather than swap**
|
||||
2.4.1 why 'memory+swap' rather than swap
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The global LRU(kswapd) can swap out arbitrary pages. Swap-out means
|
||||
to move account from memory to swap...there is no change in usage of
|
||||
@@ -252,7 +255,8 @@ memory+swap. In other words, when we want to limit the usage of swap without
|
||||
affecting global LRU, memory+swap limit is better than just limiting swap from
|
||||
an OS point of view.
|
||||
|
||||
**What happens when a cgroup hits memory.memsw.limit_in_bytes**
|
||||
2.4.2. What happens when a cgroup hits memory.memsw.limit_in_bytes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When a cgroup hits memory.memsw.limit_in_bytes, it's useless to do swap-out
|
||||
in this cgroup. Then, swap-out will not be done by cgroup routine and file
|
||||
@@ -268,26 +272,26 @@ global VM. When a cgroup goes over its limit, we first try
|
||||
to reclaim memory from the cgroup so as to make space for the new
|
||||
pages that the cgroup has touched. If the reclaim is unsuccessful,
|
||||
an OOM routine is invoked to select and kill the bulkiest task in the
|
||||
cgroup. (See 10. OOM Control below.)
|
||||
cgroup. (See :ref:`10. OOM Control <cgroup-v1-memory-oom-control>` below.)
|
||||
|
||||
The reclaim algorithm has not been modified for cgroups, except that
|
||||
pages that are selected for reclaiming come from the per-cgroup LRU
|
||||
list.
|
||||
|
||||
NOTE:
|
||||
Reclaim does not work for the root cgroup, since we cannot set any
|
||||
limits on the root cgroup.
|
||||
.. note::
|
||||
Reclaim does not work for the root cgroup, since we cannot set any
|
||||
limits on the root cgroup.
|
||||
|
||||
Note2:
|
||||
When panic_on_oom is set to "2", the whole system will panic.
|
||||
.. note::
|
||||
When panic_on_oom is set to "2", the whole system will panic.
|
||||
|
||||
When oom event notifier is registered, event will be delivered.
|
||||
(See oom_control section)
|
||||
(See :ref:`oom_control <cgroup-v1-memory-oom-control>` section)
|
||||
|
||||
2.6 Locking
|
||||
-----------
|
||||
|
||||
Lock order is as follows:
|
||||
Lock order is as follows::
|
||||
|
||||
Page lock (PG_locked bit of page->flags)
|
||||
mm->page_table_lock or split pte_lock
|
||||
@@ -299,6 +303,8 @@ Per-node-per-memcgroup LRU (cgroup's private LRU) is guarded by
|
||||
lruvec->lru_lock; PG_lru bit of page->flags is cleared before
|
||||
isolating a page from its LRU under lruvec->lru_lock.
|
||||
|
||||
.. _cgroup-v1-memory-kernel-extension:
|
||||
|
||||
2.7 Kernel Memory Extension
|
||||
-----------------------------------------------
|
||||
|
||||
@@ -367,10 +373,10 @@ U != 0, K < U:
|
||||
never greater than the total memory, and freely set U at the cost of his
|
||||
QoS.
|
||||
|
||||
WARNING:
|
||||
In the current implementation, memory reclaim will NOT be
|
||||
triggered for a cgroup when it hits K while staying below U, which makes
|
||||
this setup impractical.
|
||||
.. warning::
|
||||
In the current implementation, memory reclaim will NOT be triggered for
|
||||
a cgroup when it hits K while staying below U, which makes this setup
|
||||
impractical.
|
||||
|
||||
U != 0, K >= U:
|
||||
Since kmem charges will also be fed to the user counter and reclaim will be
|
||||
@@ -381,45 +387,41 @@ U != 0, K >= U:
|
||||
3. User Interface
|
||||
=================
|
||||
|
||||
3.0. Configuration
|
||||
------------------
|
||||
To use the user interface:
|
||||
|
||||
a. Enable CONFIG_CGROUPS
|
||||
b. Enable CONFIG_MEMCG
|
||||
|
||||
3.1. Prepare the cgroups (see cgroups.txt, Why are cgroups needed?)
|
||||
-------------------------------------------------------------------
|
||||
|
||||
::
|
||||
1. Enable CONFIG_CGROUPS and CONFIG_MEMCG options
|
||||
2. Prepare the cgroups (see :ref:`Why are cgroups needed?
|
||||
<cgroups-why-needed>` for the background information)::
|
||||
|
||||
# mount -t tmpfs none /sys/fs/cgroup
|
||||
# mkdir /sys/fs/cgroup/memory
|
||||
# mount -t cgroup none /sys/fs/cgroup/memory -o memory
|
||||
|
||||
3.2. Make the new group and move bash into it::
|
||||
3. Make the new group and move bash into it::
|
||||
|
||||
# mkdir /sys/fs/cgroup/memory/0
|
||||
# echo $$ > /sys/fs/cgroup/memory/0/tasks
|
||||
|
||||
Since now we're in the 0 cgroup, we can alter the memory limit::
|
||||
4. Since now we're in the 0 cgroup, we can alter the memory limit::
|
||||
|
||||
# echo 4M > /sys/fs/cgroup/memory/0/memory.limit_in_bytes
|
||||
|
||||
NOTE:
|
||||
We can use a suffix (k, K, m, M, g or G) to indicate values in kilo,
|
||||
mega or gigabytes. (Here, Kilo, Mega, Giga are Kibibytes, Mebibytes,
|
||||
Gibibytes.)
|
||||
The limit can now be queried::
|
||||
|
||||
NOTE:
|
||||
We can write "-1" to reset the ``*.limit_in_bytes(unlimited)``.
|
||||
# cat /sys/fs/cgroup/memory/0/memory.limit_in_bytes
|
||||
4194304
|
||||
|
||||
NOTE:
|
||||
We cannot set limits on the root cgroup any more.
|
||||
.. note::
|
||||
We can use a suffix (k, K, m, M, g or G) to indicate values in kilo,
|
||||
mega or gigabytes. (Here, Kilo, Mega, Giga are Kibibytes, Mebibytes,
|
||||
Gibibytes.)
|
||||
|
||||
::
|
||||
.. note::
|
||||
We can write "-1" to reset the ``*.limit_in_bytes(unlimited)``.
|
||||
|
||||
.. note::
|
||||
We cannot set limits on the root cgroup any more.
|
||||
|
||||
# cat /sys/fs/cgroup/memory/0/memory.limit_in_bytes
|
||||
4194304
|
||||
|
||||
We can check the usage::
|
||||
|
||||
@@ -458,6 +460,8 @@ test because it has noise of shared objects/status.
|
||||
But the above two are testing extreme situations.
|
||||
Trying usual test under memory controller is always helpful.
|
||||
|
||||
.. _cgroup-v1-memory-test-troubleshoot:
|
||||
|
||||
4.1 Troubleshooting
|
||||
-------------------
|
||||
|
||||
@@ -470,8 +474,11 @@ terminated by the OOM killer. There are several causes for this:
|
||||
A sync followed by echo 1 > /proc/sys/vm/drop_caches will help get rid of
|
||||
some of the pages cached in the cgroup (page cache pages).
|
||||
|
||||
To know what happens, disabling OOM_Kill as per "10. OOM Control" (below) and
|
||||
seeing what happens will be helpful.
|
||||
To know what happens, disabling OOM_Kill as per :ref:`"10. OOM Control"
|
||||
<cgroup-v1-memory-oom-control>` (below) and seeing what happens will be
|
||||
helpful.
|
||||
|
||||
.. _cgroup-v1-memory-test-task-migration:
|
||||
|
||||
4.2 Task migration
|
||||
------------------
|
||||
@@ -482,15 +489,16 @@ remain charged to it, the charge is dropped when the page is freed or
|
||||
reclaimed.
|
||||
|
||||
You can move charges of a task along with task migration.
|
||||
See 8. "Move charges at task migration"
|
||||
See :ref:`8. "Move charges at task migration" <cgroup-v1-memory-move-charges>`
|
||||
|
||||
4.3 Removing a cgroup
|
||||
---------------------
|
||||
|
||||
A cgroup can be removed by rmdir, but as discussed in sections 4.1 and 4.2, a
|
||||
cgroup might have some charge associated with it, even though all
|
||||
tasks have migrated away from it. (because we charge against pages, not
|
||||
against tasks.)
|
||||
A cgroup can be removed by rmdir, but as discussed in :ref:`sections 4.1
|
||||
<cgroup-v1-memory-test-troubleshoot>` and :ref:`4.2
|
||||
<cgroup-v1-memory-test-task-migration>`, a cgroup might have some charge
|
||||
associated with it, even though all tasks have migrated away from it. (because
|
||||
we charge against pages, not against tasks.)
|
||||
|
||||
We move the stats to parent, and no change on the charge except uncharging
|
||||
from the child.
|
||||
@@ -519,67 +527,66 @@ will be charged as a new owner of it.
|
||||
5.2 stat file
|
||||
-------------
|
||||
|
||||
memory.stat file includes following statistics
|
||||
memory.stat file includes following statistics:
|
||||
|
||||
per-memory cgroup local status
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* per-memory cgroup local status
|
||||
|
||||
=============== ===============================================================
|
||||
cache # of bytes of page cache memory.
|
||||
rss # of bytes of anonymous and swap cache memory (includes
|
||||
transparent hugepages).
|
||||
rss_huge # of bytes of anonymous transparent hugepages.
|
||||
mapped_file # of bytes of mapped file (includes tmpfs/shmem)
|
||||
pgpgin # of charging events to the memory cgroup. The charging
|
||||
event happens each time a page is accounted as either mapped
|
||||
anon page(RSS) or cache page(Page Cache) to the cgroup.
|
||||
pgpgout # of uncharging events to the memory cgroup. The uncharging
|
||||
event happens each time a page is unaccounted from the cgroup.
|
||||
swap # of bytes of swap usage
|
||||
dirty # of bytes that are waiting to get written back to the disk.
|
||||
writeback # of bytes of file/anon cache that are queued for syncing to
|
||||
disk.
|
||||
inactive_anon # of bytes of anonymous and swap cache memory on inactive
|
||||
LRU list.
|
||||
active_anon # of bytes of anonymous and swap cache memory on active
|
||||
LRU list.
|
||||
inactive_file # of bytes of file-backed memory and MADV_FREE anonymous memory(
|
||||
LazyFree pages) on inactive LRU list.
|
||||
active_file # of bytes of file-backed memory on active LRU list.
|
||||
unevictable # of bytes of memory that cannot be reclaimed (mlocked etc).
|
||||
=============== ===============================================================
|
||||
=============== ===============================================================
|
||||
cache # of bytes of page cache memory.
|
||||
rss # of bytes of anonymous and swap cache memory (includes
|
||||
transparent hugepages).
|
||||
rss_huge # of bytes of anonymous transparent hugepages.
|
||||
mapped_file # of bytes of mapped file (includes tmpfs/shmem)
|
||||
pgpgin # of charging events to the memory cgroup. The charging
|
||||
event happens each time a page is accounted as either mapped
|
||||
anon page(RSS) or cache page(Page Cache) to the cgroup.
|
||||
pgpgout # of uncharging events to the memory cgroup. The uncharging
|
||||
event happens each time a page is unaccounted from the
|
||||
cgroup.
|
||||
swap # of bytes of swap usage
|
||||
dirty # of bytes that are waiting to get written back to the disk.
|
||||
writeback # of bytes of file/anon cache that are queued for syncing to
|
||||
disk.
|
||||
inactive_anon # of bytes of anonymous and swap cache memory on inactive
|
||||
LRU list.
|
||||
active_anon # of bytes of anonymous and swap cache memory on active
|
||||
LRU list.
|
||||
inactive_file # of bytes of file-backed memory and MADV_FREE anonymous
|
||||
memory (LazyFree pages) on inactive LRU list.
|
||||
active_file # of bytes of file-backed memory on active LRU list.
|
||||
unevictable # of bytes of memory that cannot be reclaimed (mlocked etc).
|
||||
=============== ===============================================================
|
||||
|
||||
status considering hierarchy (see memory.use_hierarchy settings)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* status considering hierarchy (see memory.use_hierarchy settings):
|
||||
|
||||
========================= ===================================================
|
||||
hierarchical_memory_limit # of bytes of memory limit with regard to hierarchy
|
||||
under which the memory cgroup is
|
||||
hierarchical_memsw_limit # of bytes of memory+swap limit with regard to
|
||||
hierarchy under which memory cgroup is.
|
||||
========================= ===================================================
|
||||
hierarchical_memory_limit # of bytes of memory limit with regard to
|
||||
hierarchy
|
||||
under which the memory cgroup is
|
||||
hierarchical_memsw_limit # of bytes of memory+swap limit with regard to
|
||||
hierarchy under which memory cgroup is.
|
||||
|
||||
total_<counter> # hierarchical version of <counter>, which in
|
||||
addition to the cgroup's own value includes the
|
||||
sum of all hierarchical children's values of
|
||||
<counter>, i.e. total_cache
|
||||
========================= ===================================================
|
||||
total_<counter> # hierarchical version of <counter>, which in
|
||||
addition to the cgroup's own value includes the
|
||||
sum of all hierarchical children's values of
|
||||
<counter>, i.e. total_cache
|
||||
========================= ===================================================
|
||||
|
||||
The following additional stats are dependent on CONFIG_DEBUG_VM
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* additional vm parameters (depends on CONFIG_DEBUG_VM):
|
||||
|
||||
========================= ========================================
|
||||
recent_rotated_anon VM internal parameter. (see mm/vmscan.c)
|
||||
recent_rotated_file VM internal parameter. (see mm/vmscan.c)
|
||||
recent_scanned_anon VM internal parameter. (see mm/vmscan.c)
|
||||
recent_scanned_file VM internal parameter. (see mm/vmscan.c)
|
||||
========================= ========================================
|
||||
========================= ========================================
|
||||
recent_rotated_anon VM internal parameter. (see mm/vmscan.c)
|
||||
recent_rotated_file VM internal parameter. (see mm/vmscan.c)
|
||||
recent_scanned_anon VM internal parameter. (see mm/vmscan.c)
|
||||
recent_scanned_file VM internal parameter. (see mm/vmscan.c)
|
||||
========================= ========================================
|
||||
|
||||
Memo:
|
||||
.. hint::
|
||||
recent_rotated means recent frequency of LRU rotation.
|
||||
recent_scanned means recent # of scans to LRU.
|
||||
showing for better debug please see the code for meanings.
|
||||
|
||||
Note:
|
||||
.. note::
|
||||
Only anonymous and swap cache memory is listed as part of 'rss' stat.
|
||||
This should not be confused with the true 'resident set size' or the
|
||||
amount of physical memory used by the cgroup.
|
||||
@@ -710,13 +717,16 @@ If we want to change this to 1G, we can at any time use::
|
||||
|
||||
# echo 1G > memory.soft_limit_in_bytes
|
||||
|
||||
NOTE1:
|
||||
.. note::
|
||||
Soft limits take effect over a long period of time, since they involve
|
||||
reclaiming memory for balancing between memory cgroups
|
||||
NOTE2:
|
||||
|
||||
.. note::
|
||||
It is recommended to set the soft limit always below the hard limit,
|
||||
otherwise the hard limit will take precedence.
|
||||
|
||||
.. _cgroup-v1-memory-move-charges:
|
||||
|
||||
8. Move charges at task migration
|
||||
=================================
|
||||
|
||||
@@ -735,23 +745,29 @@ If you want to enable it::
|
||||
|
||||
# echo (some positive value) > memory.move_charge_at_immigrate
|
||||
|
||||
Note:
|
||||
.. note::
|
||||
Each bits of move_charge_at_immigrate has its own meaning about what type
|
||||
of charges should be moved. See 8.2 for details.
|
||||
Note:
|
||||
of charges should be moved. See :ref:`section 8.2
|
||||
<cgroup-v1-memory-movable-charges>` for details.
|
||||
|
||||
.. note::
|
||||
Charges are moved only when you move mm->owner, in other words,
|
||||
a leader of a thread group.
|
||||
Note:
|
||||
|
||||
.. note::
|
||||
If we cannot find enough space for the task in the destination cgroup, we
|
||||
try to make space by reclaiming memory. Task migration may fail if we
|
||||
cannot make enough space.
|
||||
Note:
|
||||
|
||||
.. note::
|
||||
It can take several seconds if you move charges much.
|
||||
|
||||
And if you want disable it again::
|
||||
|
||||
# echo 0 > memory.move_charge_at_immigrate
|
||||
|
||||
.. _cgroup-v1-memory-movable-charges:
|
||||
|
||||
8.2 Type of charges which can be moved
|
||||
--------------------------------------
|
||||
|
||||
@@ -801,6 +817,8 @@ threshold in any direction.
|
||||
|
||||
It's applicable for root and non-root cgroup.
|
||||
|
||||
.. _cgroup-v1-memory-oom-control:
|
||||
|
||||
10. OOM Control
|
||||
===============
|
||||
|
||||
@@ -956,15 +974,16 @@ commented and discussed quite extensively in the community.
|
||||
References
|
||||
==========
|
||||
|
||||
1. Singh, Balbir. RFC: Memory Controller, http://lwn.net/Articles/206697/
|
||||
2. Singh, Balbir. Memory Controller (RSS Control),
|
||||
.. [1] Singh, Balbir. RFC: Memory Controller, http://lwn.net/Articles/206697/
|
||||
.. [2] Singh, Balbir. Memory Controller (RSS Control),
|
||||
http://lwn.net/Articles/222762/
|
||||
3. Emelianov, Pavel. Resource controllers based on process cgroups
|
||||
.. [3] Emelianov, Pavel. Resource controllers based on process cgroups
|
||||
https://lore.kernel.org/r/45ED7DEC.7010403@sw.ru
|
||||
4. Emelianov, Pavel. RSS controller based on process cgroups (v2)
|
||||
.. [4] Emelianov, Pavel. RSS controller based on process cgroups (v2)
|
||||
https://lore.kernel.org/r/461A3010.90403@sw.ru
|
||||
5. Emelianov, Pavel. RSS controller based on process cgroups (v3)
|
||||
.. [5] Emelianov, Pavel. RSS controller based on process cgroups (v3)
|
||||
https://lore.kernel.org/r/465D9739.8070209@openvz.org
|
||||
|
||||
6. Menage, Paul. Control Groups v10, http://lwn.net/Articles/236032/
|
||||
7. Vaidyanathan, Srinivasan, Control Groups: Pagecache accounting and control
|
||||
subsystem (v3), http://lwn.net/Articles/235534/
|
||||
@@ -974,7 +993,8 @@ References
|
||||
https://lore.kernel.org/r/464D267A.50107@linux.vnet.ibm.com
|
||||
10. Singh, Balbir. Memory controller v6 test results,
|
||||
https://lore.kernel.org/r/20070819094658.654.84837.sendpatchset@balbir-laptop
|
||||
11. Singh, Balbir. Memory controller introduction (v6),
|
||||
https://lore.kernel.org/r/20070817084228.26003.12568.sendpatchset@balbir-laptop
|
||||
12. Corbet, Jonathan, Controlling memory use in cgroups,
|
||||
http://lwn.net/Articles/243795/
|
||||
|
||||
.. [11] Singh, Balbir. Memory controller introduction (v6),
|
||||
https://lore.kernel.org/r/20070817084228.26003.12568.sendpatchset@balbir-laptop
|
||||
.. [12] Corbet, Jonathan, Controlling memory use in cgroups,
|
||||
http://lwn.net/Articles/243795/
|
||||
|
||||
@@ -5121,6 +5121,17 @@
|
||||
rcupdate.rcu_cpu_stall_timeout to be used (after
|
||||
conversion from seconds to milliseconds).
|
||||
|
||||
rcupdate.rcu_cpu_stall_cputime= [KNL]
|
||||
Provide statistics on the cputime and count of
|
||||
interrupts and tasks during the sampling period. For
|
||||
multiple continuous RCU stalls, all sampling periods
|
||||
begin at half of the first RCU stall timeout.
|
||||
|
||||
rcupdate.rcu_exp_stall_task_details= [KNL]
|
||||
Print stack dumps of any tasks blocking the
|
||||
current expedited RCU grace period during an
|
||||
expedited RCU CPU stall warning.
|
||||
|
||||
rcupdate.rcu_expedited= [KNL]
|
||||
Use expedited grace-period primitives, for
|
||||
example, synchronize_rcu_expedited() instead
|
||||
@@ -7038,3 +7049,10 @@
|
||||
management firmware translates the requests into actual
|
||||
hardware states (core frequency, data fabric and memory
|
||||
clocks etc.)
|
||||
active
|
||||
Use amd_pstate_epp driver instance as the scaling driver,
|
||||
driver provides a hint to the hardware if software wants
|
||||
to bias toward performance (0x0) or energy efficiency (0xff)
|
||||
to the CPPC firmware. then CPPC power algorithm will
|
||||
calculate the runtime workload and adjust the realtime cores
|
||||
frequency.
|
||||
|
||||
@@ -230,8 +230,8 @@ with :c:macro:`MSR_AMD_CPPC_ENABLE` or ``cppc_set_enable``, it will respond
|
||||
to the request from AMD P-States.
|
||||
|
||||
|
||||
User Space Interface in ``sysfs``
|
||||
==================================
|
||||
User Space Interface in ``sysfs`` - Per-policy control
|
||||
======================================================
|
||||
|
||||
``amd-pstate`` exposes several global attributes (files) in ``sysfs`` to
|
||||
control its functionality at the system level. They are located in the
|
||||
@@ -262,6 +262,25 @@ lowest non-linear performance in `AMD CPPC Performance Capability
|
||||
<perf_cap_>`_.)
|
||||
This attribute is read-only.
|
||||
|
||||
``energy_performance_available_preferences``
|
||||
|
||||
A list of all the supported EPP preferences that could be used for
|
||||
``energy_performance_preference`` on this system.
|
||||
These profiles represent different hints that are provided
|
||||
to the low-level firmware about the user's desired energy vs efficiency
|
||||
tradeoff. ``default`` represents the epp value is set by platform
|
||||
firmware. This attribute is read-only.
|
||||
|
||||
``energy_performance_preference``
|
||||
|
||||
The current energy performance preference can be read from this attribute.
|
||||
and user can change current preference according to energy or performance needs
|
||||
Please get all support profiles list from
|
||||
``energy_performance_available_preferences`` attribute, all the profiles are
|
||||
integer values defined between 0 to 255 when EPP feature is enabled by platform
|
||||
firmware, if EPP feature is disabled, driver will ignore the written value
|
||||
This attribute is read-write.
|
||||
|
||||
Other performance and frequency values can be read back from
|
||||
``/sys/devices/system/cpu/cpuX/acpi_cppc/``, see :ref:`cppc_sysfs`.
|
||||
|
||||
@@ -280,8 +299,30 @@ module which supports the new AMD P-States mechanism on most of the future AMD
|
||||
platforms. The AMD P-States mechanism is the more performance and energy
|
||||
efficiency frequency management method on AMD processors.
|
||||
|
||||
Kernel Module Options for ``amd-pstate``
|
||||
=========================================
|
||||
|
||||
AMD Pstate Driver Operation Modes
|
||||
=================================
|
||||
|
||||
``amd_pstate`` CPPC has two operation modes: CPPC Autonomous(active) mode and
|
||||
CPPC non-autonomous(passive) mode.
|
||||
active mode and passive mode can be chosen by different kernel parameters.
|
||||
When in Autonomous mode, CPPC ignores requests done in the Desired Performance
|
||||
Target register and takes into account only the values set to the Minimum requested
|
||||
performance, Maximum requested performance, and Energy Performance Preference
|
||||
registers. When Autonomous is disabled, it only considers the Desired Performance Target.
|
||||
|
||||
Active Mode
|
||||
------------
|
||||
|
||||
``amd_pstate=active``
|
||||
|
||||
This is the low-level firmware control mode which is implemented by ``amd_pstate_epp``
|
||||
driver with ``amd_pstate=active`` passed to the kernel in the command line.
|
||||
In this mode, ``amd_pstate_epp`` driver provides a hint to the hardware if software
|
||||
wants to bias toward performance (0x0) or energy efficiency (0xff) to the CPPC firmware.
|
||||
then CPPC power algorithm will calculate the runtime workload and adjust the realtime
|
||||
cores frequency according to the power supply and thermal, core voltage and some other
|
||||
hardware conditions.
|
||||
|
||||
Passive Mode
|
||||
------------
|
||||
@@ -298,6 +339,35 @@ processor must provide at least nominal performance requested and go higher if c
|
||||
operating conditions allow.
|
||||
|
||||
|
||||
User Space Interface in ``sysfs`` - General
|
||||
===========================================
|
||||
|
||||
Global Attributes
|
||||
-----------------
|
||||
|
||||
``amd-pstate`` exposes several global attributes (files) in ``sysfs`` to
|
||||
control its functionality at the system level. They are located in the
|
||||
``/sys/devices/system/cpu/amd-pstate/`` directory and affect all CPUs.
|
||||
|
||||
``status``
|
||||
Operation mode of the driver: "active", "passive" or "disable".
|
||||
|
||||
"active"
|
||||
The driver is functional and in the ``active mode``
|
||||
|
||||
"passive"
|
||||
The driver is functional and in the ``passive mode``
|
||||
|
||||
"disable"
|
||||
The driver is unregistered and not functional now.
|
||||
|
||||
This attribute can be written to in order to change the driver's
|
||||
operation mode or to unregister it. The string written to it must be
|
||||
one of the possible values of it and, if successful, writing one of
|
||||
these values to the sysfs file will cause the driver to switch over
|
||||
to the operation mode represented by that string - or to be
|
||||
unregistered in the "disable" case.
|
||||
|
||||
``cpupower`` tool support for ``amd-pstate``
|
||||
===============================================
|
||||
|
||||
|
||||
@@ -26,8 +26,13 @@ properties:
|
||||
items:
|
||||
- enum:
|
||||
- qcom,qdu1000-cpufreq-epss
|
||||
- qcom,sc7280-cpufreq-epss
|
||||
- qcom,sc8280xp-cpufreq-epss
|
||||
- qcom,sm6375-cpufreq-epss
|
||||
- qcom,sm8250-cpufreq-epss
|
||||
- qcom,sm8350-cpufreq-epss
|
||||
- qcom,sm8450-cpufreq-epss
|
||||
- qcom,sm8550-cpufreq-epss
|
||||
- const: qcom,cpufreq-epss
|
||||
|
||||
reg:
|
||||
|
||||
@@ -17,6 +17,9 @@ description: |
|
||||
on the CPU OPP in use. The CPUFreq driver sets the CPR power domain level
|
||||
according to the required OPPs defined in the CPU OPP tables.
|
||||
|
||||
For old implementation efuses are parsed to select the correct opp table and
|
||||
voltage and CPR is not supported/used.
|
||||
|
||||
select:
|
||||
properties:
|
||||
compatible:
|
||||
@@ -33,37 +36,65 @@ select:
|
||||
required:
|
||||
- compatible
|
||||
|
||||
properties:
|
||||
cpus:
|
||||
type: object
|
||||
|
||||
patternProperties:
|
||||
'^cpu@[0-9a-f]+$':
|
||||
type: object
|
||||
|
||||
properties:
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
power-domain-names:
|
||||
items:
|
||||
- const: cpr
|
||||
|
||||
required:
|
||||
- power-domains
|
||||
- power-domain-names
|
||||
|
||||
patternProperties:
|
||||
'^opp-table(-[a-z0-9]+)?$':
|
||||
if:
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
const: operating-points-v2-kryo-cpu
|
||||
then:
|
||||
$ref: /schemas/opp/opp-v2-kryo-cpu.yaml#
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
const: operating-points-v2-qcom-level
|
||||
then:
|
||||
$ref: /schemas/opp/opp-v2-qcom-level.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
const: operating-points-v2-kryo-cpu
|
||||
contains:
|
||||
enum:
|
||||
- qcom,qcs404
|
||||
|
||||
then:
|
||||
properties:
|
||||
cpus:
|
||||
type: object
|
||||
|
||||
patternProperties:
|
||||
'^cpu@[0-9a-f]+$':
|
||||
type: object
|
||||
|
||||
properties:
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
power-domain-names:
|
||||
items:
|
||||
- const: cpr
|
||||
|
||||
required:
|
||||
- power-domains
|
||||
- power-domain-names
|
||||
|
||||
patternProperties:
|
||||
'^opp-?[0-9]+$':
|
||||
required:
|
||||
- required-opps
|
||||
'^opp-table(-[a-z0-9]+)?$':
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
const: operating-points-v2-kryo-cpu
|
||||
then:
|
||||
patternProperties:
|
||||
'^opp-?[0-9]+$':
|
||||
required:
|
||||
- required-opps
|
||||
|
||||
additionalProperties: true
|
||||
|
||||
|
||||
@@ -50,12 +50,22 @@ patternProperties:
|
||||
opp-supported-hw:
|
||||
description: |
|
||||
A single 32 bit bitmap value, representing compatible HW.
|
||||
Bitmap:
|
||||
Bitmap for MSM8996 format:
|
||||
0: MSM8996, speedbin 0
|
||||
1: MSM8996, speedbin 1
|
||||
2: MSM8996, speedbin 2
|
||||
3-31: unused
|
||||
maximum: 0x7
|
||||
3: MSM8996, speedbin 3
|
||||
4-31: unused
|
||||
|
||||
Bitmap for MSM8996SG format (speedbin shifted of 4 left):
|
||||
0-3: unused
|
||||
4: MSM8996SG, speedbin 0
|
||||
5: MSM8996SG, speedbin 1
|
||||
6: MSM8996SG, speedbin 2
|
||||
7-31: unused
|
||||
enum: [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
|
||||
0x9, 0xd, 0xe, 0xf,
|
||||
0x10, 0x20, 0x30, 0x70]
|
||||
|
||||
clock-latency-ns: true
|
||||
|
||||
@@ -106,6 +116,7 @@ examples:
|
||||
L2_0: l2-cache {
|
||||
compatible = "cache";
|
||||
cache-level = <2>;
|
||||
cache-unified;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -140,6 +151,7 @@ examples:
|
||||
L2_1: l2-cache {
|
||||
compatible = "cache";
|
||||
cache-level = <2>;
|
||||
cache-unified;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ patternProperties:
|
||||
this OPP node. Sometimes several corners/levels shares a certain fuse
|
||||
corner/level. A fuse corner/level contains e.g. ref uV, min uV,
|
||||
and max uV.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
required:
|
||||
- opp-level
|
||||
|
||||
@@ -34,7 +34,7 @@ state upon the last _LID evaluation. There won't be difference when the
|
||||
_LID control method is evaluated during the runtime, the problem is its
|
||||
initial returning value. When the AML tables implement this control method
|
||||
with cached value, the initial returning value is likely not reliable.
|
||||
There are platforms always retun "closed" as initial lid state.
|
||||
There are platforms always return "closed" as initial lid state.
|
||||
|
||||
Restrictions of the lid state change notifications
|
||||
==================================================
|
||||
|
||||
@@ -67,17 +67,30 @@ state of the output pin which driver should use during its initialization.
|
||||
Linux tries to use common sense here and derives the state from the bias
|
||||
and polarity settings. The table below shows the expectations:
|
||||
|
||||
========= ============= ==============
|
||||
Pull Bias Polarity Requested...
|
||||
========= ============= ==============
|
||||
Implicit x AS IS (assumed firmware configured for us)
|
||||
Explicit x (no _DSD) as Pull Bias (Up == High, Down == Low),
|
||||
assuming non-active (Polarity = !Pull Bias)
|
||||
Down Low as low, assuming active
|
||||
Down High as low, assuming non-active
|
||||
Up Low as high, assuming non-active
|
||||
Up High as high, assuming active
|
||||
========= ============= ==============
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
| Pull Bias | Polarity | Requested... |
|
||||
+=============+=============+===============================================+
|
||||
| Implicit |
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
| **Default** | x | AS IS (assumed firmware configured it for us) |
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
| Explicit |
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
| **None** | x | AS IS (assumed firmware configured it for us) |
|
||||
| | | with no Pull Bias |
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
| **Up** | x (no _DSD) | |
|
||||
| +-------------+ as high, assuming non-active |
|
||||
| | Low | |
|
||||
| +-------------+-----------------------------------------------+
|
||||
| | High | as high, assuming active |
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
| **Down** | x (no _DSD) | |
|
||||
| +-------------+ as low, assuming non-active |
|
||||
| | High | |
|
||||
| +-------------+-----------------------------------------------+
|
||||
| | Low | as low, assuming active |
|
||||
+-------------+-------------+-----------------------------------------------+
|
||||
|
||||
That said, for our above example the both GPIOs, since the bias setting
|
||||
is explicit and _DSD is present, will be treated as active with a high
|
||||
|
||||
@@ -31,7 +31,7 @@ Description Table). The XSDT always points to the FADT (Fixed ACPI
|
||||
Description Table) using its first entry, the data within the FADT
|
||||
includes various fixed-length entries that describe fixed ACPI features
|
||||
of the hardware. The FADT contains a pointer to the DSDT
|
||||
(Differentiated System Descripition Table). The XSDT also contains
|
||||
(Differentiated System Description Table). The XSDT also contains
|
||||
entries pointing to possibly multiple SSDTs (Secondary System
|
||||
Description Table).
|
||||
|
||||
|
||||
@@ -1277,11 +1277,11 @@ Manfred Spraul points out that you can still do this, even if the data
|
||||
is very occasionally accessed in user context or softirqs/tasklets. The
|
||||
irq handler doesn't use a lock, and all other accesses are done as so::
|
||||
|
||||
spin_lock(&lock);
|
||||
mutex_lock(&lock);
|
||||
disable_irq(irq);
|
||||
...
|
||||
enable_irq(irq);
|
||||
spin_unlock(&lock);
|
||||
mutex_unlock(&lock);
|
||||
|
||||
The disable_irq() prevents the irq handler from running
|
||||
(and waits for it to finish if it's currently running on other CPUs).
|
||||
|
||||
@@ -67,7 +67,7 @@ That may involve turning on a special signal handling logic within the platform
|
||||
during system sleep so as to trigger a system wakeup when needed. For example,
|
||||
the platform may include a dedicated interrupt controller used specifically for
|
||||
handling system wakeup events. Then, if a given interrupt line is supposed to
|
||||
wake up the system from sleep sates, the corresponding input of that interrupt
|
||||
wake up the system from sleep states, the corresponding input of that interrupt
|
||||
controller needs to be enabled to receive signals from the line in question.
|
||||
After wakeup, it generally is better to disable that input to prevent the
|
||||
dedicated controller from triggering interrupts unnecessarily.
|
||||
|
||||
@@ -1307,11 +1307,11 @@ se i dati vengono occasionalmente utilizzati da un contesto utente o
|
||||
da un'interruzione software. Il gestore d'interruzione non utilizza alcun
|
||||
*lock*, e tutti gli altri accessi verranno fatti così::
|
||||
|
||||
spin_lock(&lock);
|
||||
mutex_lock(&lock);
|
||||
disable_irq(irq);
|
||||
...
|
||||
enable_irq(irq);
|
||||
spin_unlock(&lock);
|
||||
mutex_unlock(&lock);
|
||||
|
||||
La funzione disable_irq() impedisce al gestore d'interruzioni
|
||||
d'essere eseguito (e aspetta che finisca nel caso fosse in esecuzione su
|
||||
|
||||
+6
-1
@@ -361,6 +361,8 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
|
||||
F: Documentation/ABI/testing/configfs-acpi
|
||||
F: Documentation/ABI/testing/sysfs-bus-acpi
|
||||
F: Documentation/firmware-guide/acpi/
|
||||
F: arch/x86/kernel/acpi/
|
||||
F: arch/x86/pci/acpi.c
|
||||
F: drivers/acpi/
|
||||
F: drivers/pci/*/*acpi*
|
||||
F: drivers/pci/*acpi*
|
||||
@@ -10784,6 +10786,8 @@ L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq/core
|
||||
F: kernel/irq/
|
||||
F: include/linux/group_cpus.h
|
||||
F: lib/group_cpus.c
|
||||
|
||||
IRQCHIP DRIVERS
|
||||
M: Thomas Gleixner <tglx@linutronix.de>
|
||||
@@ -19923,7 +19927,8 @@ L: linux-pm@vger.kernel.org
|
||||
S: Supported
|
||||
B: https://bugzilla.kernel.org
|
||||
F: Documentation/power/
|
||||
F: arch/x86/kernel/acpi/
|
||||
F: arch/x86/kernel/acpi/sleep*
|
||||
F: arch/x86/kernel/acpi/wakeup*
|
||||
F: drivers/base/power/
|
||||
F: include/linux/freezer.h
|
||||
F: include/linux/pm.h
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* 0x401: for compile time BRK instruction
|
||||
* 0x800: kernel-mode BUG() and WARN() traps
|
||||
* 0x9xx: tag-based KASAN trap (allowed values 0x900 - 0x9ff)
|
||||
* 0x55xx: Undefined Behavior Sanitizer traps ('U' << 8)
|
||||
* 0x8xxx: Control-Flow Integrity traps
|
||||
*/
|
||||
#define KPROBES_BRK_IMM 0x004
|
||||
@@ -28,6 +29,8 @@
|
||||
#define BUG_BRK_IMM 0x800
|
||||
#define KASAN_BRK_IMM 0x900
|
||||
#define KASAN_BRK_MASK 0x0ff
|
||||
#define UBSAN_BRK_IMM 0x5500
|
||||
#define UBSAN_BRK_MASK 0x00ff
|
||||
|
||||
#define CFI_BRK_IMM_TARGET GENMASK(4, 0)
|
||||
#define CFI_BRK_IMM_TYPE GENMASK(9, 5)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/mm_types.h>
|
||||
#include <linux/kasan.h>
|
||||
#include <linux/ubsan.h>
|
||||
#include <linux/cfi.h>
|
||||
|
||||
#include <asm/atomic.h>
|
||||
@@ -1074,6 +1075,19 @@ static struct break_hook kasan_break_hook = {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_UBSAN_TRAP
|
||||
static int ubsan_handler(struct pt_regs *regs, unsigned long esr)
|
||||
{
|
||||
die(report_ubsan_failure(regs, esr & UBSAN_BRK_MASK), regs, esr);
|
||||
return DBG_HOOK_HANDLED;
|
||||
}
|
||||
|
||||
static struct break_hook ubsan_break_hook = {
|
||||
.fn = ubsan_handler,
|
||||
.imm = UBSAN_BRK_IMM,
|
||||
.mask = UBSAN_BRK_MASK,
|
||||
};
|
||||
#endif
|
||||
|
||||
#define esr_comment(esr) ((esr) & ESR_ELx_BRK64_ISS_COMMENT_MASK)
|
||||
|
||||
@@ -1091,6 +1105,10 @@ int __init early_brk64(unsigned long addr, unsigned long esr,
|
||||
#ifdef CONFIG_KASAN_SW_TAGS
|
||||
if ((esr_comment(esr) & ~KASAN_BRK_MASK) == KASAN_BRK_IMM)
|
||||
return kasan_handler(regs, esr) != DBG_HOOK_HANDLED;
|
||||
#endif
|
||||
#ifdef CONFIG_UBSAN_TRAP
|
||||
if ((esr_comment(esr) & ~UBSAN_BRK_MASK) == UBSAN_BRK_IMM)
|
||||
return ubsan_handler(regs, esr) != DBG_HOOK_HANDLED;
|
||||
#endif
|
||||
return bug_handler(regs, esr) != DBG_HOOK_HANDLED;
|
||||
}
|
||||
@@ -1104,6 +1122,9 @@ void __init trap_init(void)
|
||||
register_kernel_break_hook(&fault_break_hook);
|
||||
#ifdef CONFIG_KASAN_SW_TAGS
|
||||
register_kernel_break_hook(&kasan_break_hook);
|
||||
#endif
|
||||
#ifdef CONFIG_UBSAN_TRAP
|
||||
register_kernel_break_hook(&ubsan_break_hook);
|
||||
#endif
|
||||
debug_traps_init();
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (c) 2014 Zhang, Keguang <keguang.zhang@gmail.com>
|
||||
*
|
||||
* Loongson 1 CPUFreq platform support.
|
||||
*/
|
||||
|
||||
#ifndef __ASM_MACH_LOONGSON32_CPUFREQ_H
|
||||
#define __ASM_MACH_LOONGSON32_CPUFREQ_H
|
||||
|
||||
struct plat_ls1x_cpufreq {
|
||||
const char *clk_name; /* CPU clk */
|
||||
const char *osc_clk_name; /* OSC clk */
|
||||
unsigned int max_freq; /* in kHz */
|
||||
unsigned int min_freq; /* in kHz */
|
||||
};
|
||||
|
||||
#endif /* __ASM_MACH_LOONGSON32_CPUFREQ_H */
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <nand.h>
|
||||
|
||||
extern struct platform_device ls1x_uart_pdev;
|
||||
extern struct platform_device ls1x_cpufreq_pdev;
|
||||
extern struct platform_device ls1x_eth0_pdev;
|
||||
extern struct platform_device ls1x_eth1_pdev;
|
||||
extern struct platform_device ls1x_ehci_pdev;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include <platform.h>
|
||||
#include <loongson1.h>
|
||||
#include <cpufreq.h>
|
||||
#include <dma.h>
|
||||
#include <nand.h>
|
||||
|
||||
@@ -62,21 +61,6 @@ void __init ls1x_serial_set_uartclk(struct platform_device *pdev)
|
||||
p->uartclk = clk_get_rate(clk);
|
||||
}
|
||||
|
||||
/* CPUFreq */
|
||||
static struct plat_ls1x_cpufreq ls1x_cpufreq_pdata = {
|
||||
.clk_name = "cpu_clk",
|
||||
.osc_clk_name = "osc_clk",
|
||||
.max_freq = 266 * 1000,
|
||||
.min_freq = 33 * 1000,
|
||||
};
|
||||
|
||||
struct platform_device ls1x_cpufreq_pdev = {
|
||||
.name = "ls1x-cpufreq",
|
||||
.dev = {
|
||||
.platform_data = &ls1x_cpufreq_pdata,
|
||||
},
|
||||
};
|
||||
|
||||
/* Synopsys Ethernet GMAC */
|
||||
static struct stmmac_mdio_bus_data ls1x_mdio_bus_data = {
|
||||
.phy_mask = 0,
|
||||
|
||||
@@ -35,7 +35,6 @@ static const struct gpio_led_platform_data ls1x_led_pdata __initconst = {
|
||||
|
||||
static struct platform_device *ls1b_platform_devices[] __initdata = {
|
||||
&ls1x_uart_pdev,
|
||||
&ls1x_cpufreq_pdev,
|
||||
&ls1x_eth0_pdev,
|
||||
&ls1x_eth1_pdev,
|
||||
&ls1x_ehci_pdev,
|
||||
|
||||
@@ -2364,9 +2364,8 @@ static int mp_irqdomain_create(int ioapic)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ip->irqdomain = irq_domain_create_linear(fn, hwirqs, cfg->ops,
|
||||
(void *)(long)ioapic);
|
||||
|
||||
ip->irqdomain = irq_domain_create_hierarchy(parent, 0, hwirqs, fn, cfg->ops,
|
||||
(void *)(long)ioapic);
|
||||
if (!ip->irqdomain) {
|
||||
/* Release fw handle if it was allocated above */
|
||||
if (!cfg->dev)
|
||||
@@ -2374,8 +2373,6 @@ static int mp_irqdomain_create(int ioapic)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ip->irqdomain->parent = parent;
|
||||
|
||||
if (cfg->type == IOAPIC_DOMAIN_LEGACY ||
|
||||
cfg->type == IOAPIC_DOMAIN_STRICT)
|
||||
ioapic_dynirq_base = max(ioapic_dynirq_base,
|
||||
|
||||
@@ -739,6 +739,7 @@ void __cpuidle arch_cpu_idle(void)
|
||||
{
|
||||
static_call(x86_idle)();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arch_cpu_idle);
|
||||
|
||||
#ifdef CONFIG_XEN
|
||||
bool xen_set_default_idle(void)
|
||||
|
||||
@@ -166,10 +166,9 @@ static struct irq_domain *uv_get_irq_domain(void)
|
||||
if (!fn)
|
||||
goto out;
|
||||
|
||||
uv_domain = irq_domain_create_tree(fn, &uv_domain_ops, NULL);
|
||||
if (uv_domain)
|
||||
uv_domain->parent = x86_vector_domain;
|
||||
else
|
||||
uv_domain = irq_domain_create_hierarchy(x86_vector_domain, 0, 0, fn,
|
||||
&uv_domain_ops, NULL);
|
||||
if (!uv_domain)
|
||||
irq_domain_free_fwnode(fn);
|
||||
out:
|
||||
mutex_unlock(&uv_lock);
|
||||
|
||||
+12
-49
@@ -10,66 +10,29 @@
|
||||
#include <linux/mm.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/group_cpus.h>
|
||||
|
||||
#include <linux/blk-mq.h>
|
||||
#include "blk.h"
|
||||
#include "blk-mq.h"
|
||||
|
||||
static int queue_index(struct blk_mq_queue_map *qmap,
|
||||
unsigned int nr_queues, const int q)
|
||||
{
|
||||
return qmap->queue_offset + (q % nr_queues);
|
||||
}
|
||||
|
||||
static int get_first_sibling(unsigned int cpu)
|
||||
{
|
||||
unsigned int ret;
|
||||
|
||||
ret = cpumask_first(topology_sibling_cpumask(cpu));
|
||||
if (ret < nr_cpu_ids)
|
||||
return ret;
|
||||
|
||||
return cpu;
|
||||
}
|
||||
|
||||
void blk_mq_map_queues(struct blk_mq_queue_map *qmap)
|
||||
{
|
||||
unsigned int *map = qmap->mq_map;
|
||||
unsigned int nr_queues = qmap->nr_queues;
|
||||
unsigned int cpu, first_sibling, q = 0;
|
||||
const struct cpumask *masks;
|
||||
unsigned int queue, cpu;
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
map[cpu] = -1;
|
||||
|
||||
/*
|
||||
* Spread queues among present CPUs first for minimizing
|
||||
* count of dead queues which are mapped by all un-present CPUs
|
||||
*/
|
||||
for_each_present_cpu(cpu) {
|
||||
if (q >= nr_queues)
|
||||
break;
|
||||
map[cpu] = queue_index(qmap, nr_queues, q++);
|
||||
masks = group_cpus_evenly(qmap->nr_queues);
|
||||
if (!masks) {
|
||||
for_each_possible_cpu(cpu)
|
||||
qmap->mq_map[cpu] = qmap->queue_offset;
|
||||
return;
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (map[cpu] != -1)
|
||||
continue;
|
||||
/*
|
||||
* First do sequential mapping between CPUs and queues.
|
||||
* In case we still have CPUs to map, and we have some number of
|
||||
* threads per cores then map sibling threads to the same queue
|
||||
* for performance optimizations.
|
||||
*/
|
||||
if (q < nr_queues) {
|
||||
map[cpu] = queue_index(qmap, nr_queues, q++);
|
||||
} else {
|
||||
first_sibling = get_first_sibling(cpu);
|
||||
if (first_sibling == cpu)
|
||||
map[cpu] = queue_index(qmap, nr_queues, q++);
|
||||
else
|
||||
map[cpu] = map[first_sibling];
|
||||
}
|
||||
for (queue = 0; queue < qmap->nr_queues; queue++) {
|
||||
for_each_cpu(cpu, &masks[queue])
|
||||
qmap->mq_map[cpu] = qmap->queue_offset + queue;
|
||||
}
|
||||
kfree(masks);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(blk_mq_map_queues);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <linux/acpi.h>
|
||||
#include <asm/msr.h>
|
||||
#include <asm/tsc.h>
|
||||
#include "internal.h"
|
||||
|
||||
struct lpit_residency_info {
|
||||
struct acpi_generic_address gaddr;
|
||||
|
||||
+13
-1
@@ -348,10 +348,22 @@ static bool acpi_pnp_match(const char *idstr, const struct acpi_device_id **matc
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If one of the device IDs below is present in the list of device IDs of a
|
||||
* given ACPI device object, the PNP scan handler will not attach to that
|
||||
* object, because there is a proper non-PNP driver in the kernel for the
|
||||
* device represented by it.
|
||||
*/
|
||||
static const struct acpi_device_id acpi_nonpnp_device_ids[] = {
|
||||
{"INTC1080"},
|
||||
{"INTC1081"},
|
||||
{""},
|
||||
};
|
||||
|
||||
static int acpi_pnp_attach(struct acpi_device *adev,
|
||||
const struct acpi_device_id *id)
|
||||
{
|
||||
return 1;
|
||||
return !!acpi_match_device_ids(adev, acpi_nonpnp_device_ids);
|
||||
}
|
||||
|
||||
static struct acpi_scan_handler acpi_pnp_handler = {
|
||||
|
||||
@@ -23,8 +23,8 @@ acpi_hw_validate_io_request(acpi_io_address address, u32 bit_width);
|
||||
*
|
||||
* The table is used to implement the Microsoft port access rules that
|
||||
* first appeared in Windows XP. Some ports are always illegal, and some
|
||||
* ports are only illegal if the BIOS calls _OSI with a win_XP string or
|
||||
* later (meaning that the BIOS itelf is post-XP.)
|
||||
* ports are only illegal if the BIOS calls _OSI with nothing newer than
|
||||
* the specific _OSI strings.
|
||||
*
|
||||
* This provides ACPICA with the desired port protections and
|
||||
* Microsoft compatibility.
|
||||
@@ -145,7 +145,8 @@ acpi_hw_validate_io_request(acpi_io_address address, u32 bit_width)
|
||||
|
||||
/* Port illegality may depend on the _OSI calls made by the BIOS */
|
||||
|
||||
if (acpi_gbl_osi_data >= port_info->osi_dependency) {
|
||||
if (port_info->osi_dependency == ACPI_ALWAYS_ILLEGAL ||
|
||||
acpi_gbl_osi_data == port_info->osi_dependency) {
|
||||
ACPI_DEBUG_PRINT((ACPI_DB_VALUES,
|
||||
"Denied AML access to port 0x%8.8X%8.8X/%X (%s 0x%.4X-0x%.4X)\n",
|
||||
ACPI_FORMAT_UINT64(address),
|
||||
|
||||
@@ -181,8 +181,9 @@ acpi_ns_simple_repair(struct acpi_evaluate_info *info,
|
||||
* Try to fix if there was no return object. Warning if failed to fix.
|
||||
*/
|
||||
if (!return_object) {
|
||||
if (expected_btypes && (!(expected_btypes & ACPI_RTYPE_NONE))) {
|
||||
if (package_index != ACPI_NOT_PACKAGE_ELEMENT) {
|
||||
if (expected_btypes) {
|
||||
if (!(expected_btypes & ACPI_RTYPE_NONE) &&
|
||||
package_index != ACPI_NOT_PACKAGE_ELEMENT) {
|
||||
ACPI_WARN_PREDEFINED((AE_INFO,
|
||||
info->full_pathname,
|
||||
ACPI_WARN_ALWAYS,
|
||||
@@ -196,14 +197,15 @@ acpi_ns_simple_repair(struct acpi_evaluate_info *info,
|
||||
if (ACPI_SUCCESS(status)) {
|
||||
return (AE_OK); /* Repair was successful */
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (expected_btypes != ACPI_RTYPE_NONE) {
|
||||
ACPI_WARN_PREDEFINED((AE_INFO,
|
||||
info->full_pathname,
|
||||
ACPI_WARN_ALWAYS,
|
||||
"Missing expected return value"));
|
||||
return (AE_AML_NO_RETURN_VALUE);
|
||||
}
|
||||
|
||||
return (AE_AML_NO_RETURN_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ static char *acpi_ns_copy_device_id(struct acpi_pnp_device_id *dest,
|
||||
|
||||
acpi_status
|
||||
acpi_get_handle(acpi_handle parent,
|
||||
acpi_string pathname, acpi_handle *ret_handle)
|
||||
const char *pathname, acpi_handle *ret_handle)
|
||||
{
|
||||
acpi_status status;
|
||||
struct acpi_namespace_node *node = NULL;
|
||||
|
||||
@@ -616,6 +616,10 @@ static int error_type_set(void *data, u64 val)
|
||||
u32 available_error_type = 0;
|
||||
u32 tval, vendor;
|
||||
|
||||
/* Only low 32 bits for error type are valid */
|
||||
if (val & GENMASK_ULL(63, 32))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Vendor defined types have 0x80000000 bit set, and
|
||||
* are not enumerated by ACPI_EINJ_GET_ERROR_TYPE
|
||||
|
||||
+23
-12
@@ -42,6 +42,8 @@
|
||||
#define ACPI_BATTERY_STATE_CHARGING 0x2
|
||||
#define ACPI_BATTERY_STATE_CRITICAL 0x4
|
||||
|
||||
#define MAX_STRING_LENGTH 64
|
||||
|
||||
MODULE_AUTHOR("Paul Diefenbaugh");
|
||||
MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
|
||||
MODULE_DESCRIPTION("ACPI Battery Driver");
|
||||
@@ -118,10 +120,10 @@ struct acpi_battery {
|
||||
int capacity_granularity_1;
|
||||
int capacity_granularity_2;
|
||||
int alarm;
|
||||
char model_number[32];
|
||||
char serial_number[32];
|
||||
char type[32];
|
||||
char oem_info[32];
|
||||
char model_number[MAX_STRING_LENGTH];
|
||||
char serial_number[MAX_STRING_LENGTH];
|
||||
char type[MAX_STRING_LENGTH];
|
||||
char oem_info[MAX_STRING_LENGTH];
|
||||
int state;
|
||||
int power_unit;
|
||||
unsigned long flags;
|
||||
@@ -437,16 +439,25 @@ static int extract_package(struct acpi_battery *battery,
|
||||
element = &package->package.elements[i];
|
||||
if (offsets[i].mode) {
|
||||
u8 *ptr = (u8 *)battery + offsets[i].offset;
|
||||
u32 len = MAX_STRING_LENGTH;
|
||||
|
||||
if (element->type == ACPI_TYPE_STRING ||
|
||||
element->type == ACPI_TYPE_BUFFER)
|
||||
strncpy(ptr, element->string.pointer, 32);
|
||||
else if (element->type == ACPI_TYPE_INTEGER) {
|
||||
strncpy(ptr, (u8 *)&element->integer.value,
|
||||
sizeof(u64));
|
||||
ptr[sizeof(u64)] = 0;
|
||||
} else
|
||||
switch (element->type) {
|
||||
case ACPI_TYPE_BUFFER:
|
||||
if (len > element->buffer.length + 1)
|
||||
len = element->buffer.length + 1;
|
||||
|
||||
fallthrough;
|
||||
case ACPI_TYPE_STRING:
|
||||
strscpy(ptr, element->string.pointer, len);
|
||||
|
||||
break;
|
||||
case ACPI_TYPE_INTEGER:
|
||||
strscpy(ptr, (u8 *)&element->integer.value, sizeof(u64) + 1);
|
||||
|
||||
break;
|
||||
default:
|
||||
*ptr = 0; /* don't have value */
|
||||
}
|
||||
} else {
|
||||
int *x = (int *)((u8 *)battery + offsets[i].offset);
|
||||
*x = (element->type == ACPI_TYPE_INTEGER) ?
|
||||
|
||||
@@ -193,7 +193,7 @@ static struct attribute *cppc_attrs[] = {
|
||||
};
|
||||
ATTRIBUTE_GROUPS(cppc);
|
||||
|
||||
static struct kobj_type cppc_ktype = {
|
||||
static const struct kobj_type cppc_ktype = {
|
||||
.sysfs_ops = &kobj_sysfs_ops,
|
||||
.default_groups = cppc_groups,
|
||||
};
|
||||
@@ -595,6 +595,7 @@ bool __weak cpc_supported_by_cpu(void)
|
||||
|
||||
/**
|
||||
* pcc_data_alloc() - Allocate the pcc_data memory for pcc subspace
|
||||
* @pcc_ss_id: PCC Subspace index as in the PCC client ACPI package.
|
||||
*
|
||||
* Check and allocate the cppc_pcc_data memory.
|
||||
* In some processor configurations it is possible that same subspace
|
||||
@@ -1153,6 +1154,19 @@ int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf)
|
||||
return cppc_get_perf(cpunum, NOMINAL_PERF, nominal_perf);
|
||||
}
|
||||
|
||||
/**
|
||||
* cppc_get_epp_perf - Get the epp register value.
|
||||
* @cpunum: CPU from which to get epp preference value.
|
||||
* @epp_perf: Return address.
|
||||
*
|
||||
* Return: 0 for success, -EIO otherwise.
|
||||
*/
|
||||
int cppc_get_epp_perf(int cpunum, u64 *epp_perf)
|
||||
{
|
||||
return cppc_get_perf(cpunum, ENERGY_PERF, epp_perf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cppc_get_epp_perf);
|
||||
|
||||
/**
|
||||
* cppc_get_perf_caps - Get a CPU's performance capabilities.
|
||||
* @cpunum: CPU from which to get capabilities info.
|
||||
@@ -1365,6 +1379,60 @@ out_err:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cppc_get_perf_ctrs);
|
||||
|
||||
/*
|
||||
* Set Energy Performance Preference Register value through
|
||||
* Performance Controls Interface
|
||||
*/
|
||||
int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable)
|
||||
{
|
||||
int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu);
|
||||
struct cpc_register_resource *epp_set_reg;
|
||||
struct cpc_register_resource *auto_sel_reg;
|
||||
struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
|
||||
struct cppc_pcc_data *pcc_ss_data = NULL;
|
||||
int ret;
|
||||
|
||||
if (!cpc_desc) {
|
||||
pr_debug("No CPC descriptor for CPU:%d\n", cpu);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
auto_sel_reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE];
|
||||
epp_set_reg = &cpc_desc->cpc_regs[ENERGY_PERF];
|
||||
|
||||
if (CPC_IN_PCC(epp_set_reg) || CPC_IN_PCC(auto_sel_reg)) {
|
||||
if (pcc_ss_id < 0) {
|
||||
pr_debug("Invalid pcc_ss_id for CPU:%d\n", cpu);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (CPC_SUPPORTED(auto_sel_reg)) {
|
||||
ret = cpc_write(cpu, auto_sel_reg, enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (CPC_SUPPORTED(epp_set_reg)) {
|
||||
ret = cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
pcc_ss_data = pcc_data[pcc_ss_id];
|
||||
|
||||
down_write(&pcc_ss_data->pcc_lock);
|
||||
/* after writing CPC, transfer the ownership of PCC to platform */
|
||||
ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE);
|
||||
up_write(&pcc_ss_data->pcc_lock);
|
||||
} else {
|
||||
ret = -ENOTSUPP;
|
||||
pr_debug("_CPC in PCC is not supported\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cppc_set_epp_perf);
|
||||
|
||||
/**
|
||||
* cppc_set_enable - Set to enable CPPC on the processor by writing the
|
||||
* Continuous Performance Control package EnableRegister field.
|
||||
@@ -1536,6 +1604,7 @@ EXPORT_SYMBOL_GPL(cppc_set_perf);
|
||||
|
||||
/**
|
||||
* cppc_get_transition_latency - returns frequency transition latency in ns
|
||||
* @cpu_num: CPU number for per_cpu().
|
||||
*
|
||||
* ACPI CPPC does not explicitly specify how a platform can specify the
|
||||
* transition latency for performance change requests. The closest we have
|
||||
|
||||
@@ -78,7 +78,7 @@ static void acpi_data_node_release(struct kobject *kobj)
|
||||
complete(&dn->kobj_done);
|
||||
}
|
||||
|
||||
static struct kobj_type acpi_data_node_ktype = {
|
||||
static const struct kobj_type acpi_data_node_ktype = {
|
||||
.sysfs_ops = &acpi_data_node_sysfs_ops,
|
||||
.default_groups = acpi_data_node_default_groups,
|
||||
.release = acpi_data_node_release,
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <acpi/acpi.h>
|
||||
#include "internal.h"
|
||||
|
||||
struct acpi_pci_ioapic {
|
||||
acpi_handle root_handle;
|
||||
|
||||
@@ -283,6 +283,7 @@ static const struct intel_pmic_opregion_data intel_crc_pmic_opregion_data = {
|
||||
.power_table_count= ARRAY_SIZE(power_table),
|
||||
.thermal_table = thermal_table,
|
||||
.thermal_table_count = ARRAY_SIZE(thermal_table),
|
||||
.pmic_i2c_address = 0x6e,
|
||||
};
|
||||
|
||||
static int intel_crc_pmic_opregion_probe(struct platform_device *pdev)
|
||||
|
||||
@@ -20,19 +20,19 @@
|
||||
#define CHTDC_TI_GPADC 0x5a
|
||||
|
||||
static struct pmic_table chtdc_ti_power_table[] = {
|
||||
{ .address = 0x00, .reg = 0x41 },
|
||||
{ .address = 0x04, .reg = 0x42 },
|
||||
{ .address = 0x08, .reg = 0x43 },
|
||||
{ .address = 0x0c, .reg = 0x45 },
|
||||
{ .address = 0x10, .reg = 0x46 },
|
||||
{ .address = 0x14, .reg = 0x47 },
|
||||
{ .address = 0x18, .reg = 0x48 },
|
||||
{ .address = 0x1c, .reg = 0x49 },
|
||||
{ .address = 0x20, .reg = 0x4a },
|
||||
{ .address = 0x24, .reg = 0x4b },
|
||||
{ .address = 0x28, .reg = 0x4c },
|
||||
{ .address = 0x2c, .reg = 0x4d },
|
||||
{ .address = 0x30, .reg = 0x4e },
|
||||
{ .address = 0x00, .reg = 0x41 }, /* LDO1 */
|
||||
{ .address = 0x04, .reg = 0x42 }, /* LDO2 */
|
||||
{ .address = 0x08, .reg = 0x43 }, /* LDO3 */
|
||||
{ .address = 0x0c, .reg = 0x45 }, /* LDO5 */
|
||||
{ .address = 0x10, .reg = 0x46 }, /* LDO6 */
|
||||
{ .address = 0x14, .reg = 0x47 }, /* LDO7 */
|
||||
{ .address = 0x18, .reg = 0x48 }, /* LDO8 */
|
||||
{ .address = 0x1c, .reg = 0x49 }, /* LDO9 */
|
||||
{ .address = 0x20, .reg = 0x4a }, /* LD10 */
|
||||
{ .address = 0x24, .reg = 0x4b }, /* LD11 */
|
||||
{ .address = 0x28, .reg = 0x4c }, /* LD12 */
|
||||
{ .address = 0x2c, .reg = 0x4d }, /* LD13 */
|
||||
{ .address = 0x30, .reg = 0x4e }, /* LD14 */
|
||||
};
|
||||
|
||||
static struct pmic_table chtdc_ti_thermal_table[] = {
|
||||
|
||||
@@ -147,7 +147,7 @@ static void lapic_timer_check_state(int state, struct acpi_processor *pr,
|
||||
|
||||
static void __lapic_timer_propagate_broadcast(void *arg)
|
||||
{
|
||||
struct acpi_processor *pr = (struct acpi_processor *) arg;
|
||||
struct acpi_processor *pr = arg;
|
||||
|
||||
if (pr->power.timer_broadcast_on_state < INT_MAX)
|
||||
tick_broadcast_enable();
|
||||
|
||||
@@ -53,6 +53,8 @@ static int acpi_processor_get_platform_limit(struct acpi_processor *pr)
|
||||
{
|
||||
acpi_status status = 0;
|
||||
unsigned long long ppc = 0;
|
||||
s32 qos_value;
|
||||
int index;
|
||||
int ret;
|
||||
|
||||
if (!pr)
|
||||
@@ -72,17 +74,30 @@ static int acpi_processor_get_platform_limit(struct acpi_processor *pr)
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("CPU %d: _PPC is %d - frequency %s limited\n", pr->id,
|
||||
(int)ppc, ppc ? "" : "not");
|
||||
index = ppc;
|
||||
|
||||
pr->performance_platform_limit = (int)ppc;
|
||||
|
||||
if (ppc >= pr->performance->state_count ||
|
||||
unlikely(!freq_qos_request_active(&pr->perflib_req)))
|
||||
if (pr->performance_platform_limit == index ||
|
||||
ppc >= pr->performance->state_count)
|
||||
return 0;
|
||||
|
||||
ret = freq_qos_update_request(&pr->perflib_req,
|
||||
pr->performance->states[ppc].core_frequency * 1000);
|
||||
pr_debug("CPU %d: _PPC is %d - frequency %s limited\n", pr->id,
|
||||
index, index ? "is" : "is not");
|
||||
|
||||
pr->performance_platform_limit = index;
|
||||
|
||||
if (unlikely(!freq_qos_request_active(&pr->perflib_req)))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If _PPC returns 0, it means that all of the available states can be
|
||||
* used ("no limit").
|
||||
*/
|
||||
if (index == 0)
|
||||
qos_value = FREQ_QOS_MAX_DEFAULT_VALUE;
|
||||
else
|
||||
qos_value = pr->performance->states[index].core_frequency * 1000;
|
||||
|
||||
ret = freq_qos_update_request(&pr->perflib_req, qos_value);
|
||||
if (ret < 0) {
|
||||
pr_warn("Failed to update perflib freq constraint: CPU%d (%d)\n",
|
||||
pr->id, ret);
|
||||
@@ -166,9 +181,16 @@ void acpi_processor_ppc_init(struct cpufreq_policy *policy)
|
||||
if (!pr)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Reset performance_platform_limit in case there is a stale
|
||||
* value in it, so as to make it match the "no limit" QoS value
|
||||
* below.
|
||||
*/
|
||||
pr->performance_platform_limit = 0;
|
||||
|
||||
ret = freq_qos_add_request(&policy->constraints,
|
||||
&pr->perflib_req,
|
||||
FREQ_QOS_MAX, INT_MAX);
|
||||
&pr->perflib_req, FREQ_QOS_MAX,
|
||||
FREQ_QOS_MAX_DEFAULT_VALUE);
|
||||
if (ret < 0)
|
||||
pr_err("Failed to add freq constraint for CPU%d (%d)\n",
|
||||
cpu, ret);
|
||||
|
||||
+22
-4
@@ -467,17 +467,34 @@ static const struct dmi_system_id lenovo_laptop[] = {
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct dmi_system_id schenker_gm_rg[] = {
|
||||
static const struct dmi_system_id tongfang_gm_rg[] = {
|
||||
{
|
||||
.ident = "XMG CORE 15 (M22)",
|
||||
.ident = "TongFang GMxRGxx/XMG CORE 15 (M22)/TUXEDO Stellaris 15 Gen4 AMD",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "SchenkerTechnologiesGmbH"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "GMxRGxx"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct dmi_system_id maingear_laptop[] = {
|
||||
{
|
||||
.ident = "MAINGEAR Vector Pro 2 15",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Micro Electronics Inc"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MG-VCP2-15A3070T"),
|
||||
}
|
||||
},
|
||||
{
|
||||
.ident = "MAINGEAR Vector Pro 2 17",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Micro Electronics Inc"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "MG-VCP2-17A3070T"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
struct irq_override_cmp {
|
||||
const struct dmi_system_id *system;
|
||||
unsigned char irq;
|
||||
@@ -492,7 +509,8 @@ static const struct irq_override_cmp override_table[] = {
|
||||
{ asus_laptop, 1, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_LOW, 0, false },
|
||||
{ lenovo_laptop, 6, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_LOW, 0, true },
|
||||
{ lenovo_laptop, 10, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_LOW, 0, true },
|
||||
{ schenker_gm_rg, 1, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_LOW, 1, true },
|
||||
{ tongfang_gm_rg, 1, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_LOW, 1, true },
|
||||
{ maingear_laptop, 1, ACPI_EDGE_SENSITIVE, ACPI_ACTIVE_LOW, 1, true },
|
||||
};
|
||||
|
||||
static bool acpi_dev_irq_override(u32 gsi, u8 triggering, u8 polarity,
|
||||
|
||||
@@ -953,7 +953,7 @@ static struct attribute *hotplug_profile_attrs[] = {
|
||||
};
|
||||
ATTRIBUTE_GROUPS(hotplug_profile);
|
||||
|
||||
static struct kobj_type acpi_hotplug_profile_ktype = {
|
||||
static const struct kobj_type acpi_hotplug_profile_ktype = {
|
||||
.sysfs_ops = &kobj_sysfs_ops,
|
||||
.default_groups = hotplug_profile_groups,
|
||||
};
|
||||
|
||||
@@ -555,7 +555,8 @@ static const char table_sigs[][ACPI_NAMESEG_SIZE] __initconst = {
|
||||
ACPI_SIG_WDDT, ACPI_SIG_WDRT, ACPI_SIG_DSDT, ACPI_SIG_FADT,
|
||||
ACPI_SIG_PSDT, ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT,
|
||||
ACPI_SIG_IORT, ACPI_SIG_NFIT, ACPI_SIG_HMAT, ACPI_SIG_PPTT,
|
||||
ACPI_SIG_NHLT, ACPI_SIG_AEST, ACPI_SIG_CEDT, ACPI_SIG_AGDI };
|
||||
ACPI_SIG_NHLT, ACPI_SIG_AEST, ACPI_SIG_CEDT, ACPI_SIG_AGDI,
|
||||
ACPI_SIG_NBFT };
|
||||
|
||||
#define ACPI_HEADER_SIZE sizeof(struct acpi_table_header)
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ static const struct dmi_system_id video_detect_dmi_table[] = {
|
||||
/* Lenovo Ideapad Z570 */
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "102434U"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "Ideapad Z570"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -181,7 +181,6 @@ void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fw_devlink_purge_absent_suppliers);
|
||||
|
||||
#ifdef CONFIG_SRCU
|
||||
static DEFINE_MUTEX(device_links_lock);
|
||||
DEFINE_STATIC_SRCU(device_links_srcu);
|
||||
|
||||
@@ -220,47 +219,6 @@ static void device_link_remove_from_lists(struct device_link *link)
|
||||
list_del_rcu(&link->s_node);
|
||||
list_del_rcu(&link->c_node);
|
||||
}
|
||||
#else /* !CONFIG_SRCU */
|
||||
static DECLARE_RWSEM(device_links_lock);
|
||||
|
||||
static inline void device_links_write_lock(void)
|
||||
{
|
||||
down_write(&device_links_lock);
|
||||
}
|
||||
|
||||
static inline void device_links_write_unlock(void)
|
||||
{
|
||||
up_write(&device_links_lock);
|
||||
}
|
||||
|
||||
int device_links_read_lock(void)
|
||||
{
|
||||
down_read(&device_links_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void device_links_read_unlock(int not_used)
|
||||
{
|
||||
up_read(&device_links_lock);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
int device_links_read_lock_held(void)
|
||||
{
|
||||
return lockdep_is_held(&device_links_lock);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void device_link_synchronize_removal(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void device_link_remove_from_lists(struct device_link *link)
|
||||
{
|
||||
list_del(&link->s_node);
|
||||
list_del(&link->c_node);
|
||||
}
|
||||
#endif /* !CONFIG_SRCU */
|
||||
|
||||
static bool device_is_ancestor(struct device *dev, struct device *target)
|
||||
{
|
||||
|
||||
@@ -220,13 +220,10 @@ static void genpd_debug_add(struct generic_pm_domain *genpd);
|
||||
|
||||
static void genpd_debug_remove(struct generic_pm_domain *genpd)
|
||||
{
|
||||
struct dentry *d;
|
||||
|
||||
if (!genpd_debugfs_dir)
|
||||
return;
|
||||
|
||||
d = debugfs_lookup(genpd->name, genpd_debugfs_dir);
|
||||
debugfs_remove(d);
|
||||
debugfs_lookup_and_remove(genpd->name, genpd_debugfs_dir);
|
||||
}
|
||||
|
||||
static void genpd_update_accounting(struct generic_pm_domain *genpd)
|
||||
|
||||
@@ -1864,6 +1864,10 @@ static bool pm_runtime_need_not_resume(struct device *dev)
|
||||
* sure the device is put into low power state and it should only be used during
|
||||
* system-wide PM transitions to sleep states. It assumes that the analogous
|
||||
* pm_runtime_force_resume() will be used to resume the device.
|
||||
*
|
||||
* Do not use with DPM_FLAG_SMART_SUSPEND as this can lead to an inconsistent
|
||||
* state where this function has called the ->runtime_suspend callback but the
|
||||
* PM core marks the driver as runtime active.
|
||||
*/
|
||||
int pm_runtime_force_suspend(struct device *dev)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ menu "CPU Frequency scaling"
|
||||
|
||||
config CPU_FREQ
|
||||
bool "CPU Frequency scaling"
|
||||
select SRCU
|
||||
help
|
||||
CPU Frequency scaling allows you to change the clock speed of
|
||||
CPUs on the fly. This is a nice method to save power, because
|
||||
@@ -286,15 +285,6 @@ config LOONGSON2_CPUFREQ
|
||||
|
||||
Loongson2F and its successors support this feature.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config LOONGSON1_CPUFREQ
|
||||
tristate "Loongson1 CPUFreq Driver"
|
||||
depends on LOONGSON1_LS1B
|
||||
help
|
||||
This option adds a CPUFreq driver for loongson1 processors which
|
||||
support software configurable cpu frequency.
|
||||
|
||||
If in doubt, say N.
|
||||
endif
|
||||
|
||||
|
||||
@@ -109,7 +109,6 @@ obj-$(CONFIG_POWERNV_CPUFREQ) += powernv-cpufreq.o
|
||||
obj-$(CONFIG_BMIPS_CPUFREQ) += bmips-cpufreq.o
|
||||
obj-$(CONFIG_IA64_ACPI_CPUFREQ) += ia64-acpi-cpufreq.o
|
||||
obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o
|
||||
obj-$(CONFIG_LOONGSON1_CPUFREQ) += loongson1-cpufreq.o
|
||||
obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o
|
||||
obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o
|
||||
obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o
|
||||
|
||||
+686
-19
@@ -59,8 +59,171 @@
|
||||
* we disable it by default to go acpi-cpufreq on these processors and add a
|
||||
* module parameter to be able to enable it manually for debugging.
|
||||
*/
|
||||
static struct cpufreq_driver *current_pstate_driver;
|
||||
static struct cpufreq_driver amd_pstate_driver;
|
||||
static int cppc_load __initdata;
|
||||
static struct cpufreq_driver amd_pstate_epp_driver;
|
||||
static int cppc_state = AMD_PSTATE_DISABLE;
|
||||
struct kobject *amd_pstate_kobj;
|
||||
|
||||
/*
|
||||
* AMD Energy Preference Performance (EPP)
|
||||
* The EPP is used in the CCLK DPM controller to drive
|
||||
* the frequency that a core is going to operate during
|
||||
* short periods of activity. EPP values will be utilized for
|
||||
* different OS profiles (balanced, performance, power savings)
|
||||
* display strings corresponding to EPP index in the
|
||||
* energy_perf_strings[]
|
||||
* index String
|
||||
*-------------------------------------
|
||||
* 0 default
|
||||
* 1 performance
|
||||
* 2 balance_performance
|
||||
* 3 balance_power
|
||||
* 4 power
|
||||
*/
|
||||
enum energy_perf_value_index {
|
||||
EPP_INDEX_DEFAULT = 0,
|
||||
EPP_INDEX_PERFORMANCE,
|
||||
EPP_INDEX_BALANCE_PERFORMANCE,
|
||||
EPP_INDEX_BALANCE_POWERSAVE,
|
||||
EPP_INDEX_POWERSAVE,
|
||||
};
|
||||
|
||||
static const char * const energy_perf_strings[] = {
|
||||
[EPP_INDEX_DEFAULT] = "default",
|
||||
[EPP_INDEX_PERFORMANCE] = "performance",
|
||||
[EPP_INDEX_BALANCE_PERFORMANCE] = "balance_performance",
|
||||
[EPP_INDEX_BALANCE_POWERSAVE] = "balance_power",
|
||||
[EPP_INDEX_POWERSAVE] = "power",
|
||||
NULL
|
||||
};
|
||||
|
||||
static unsigned int epp_values[] = {
|
||||
[EPP_INDEX_DEFAULT] = 0,
|
||||
[EPP_INDEX_PERFORMANCE] = AMD_CPPC_EPP_PERFORMANCE,
|
||||
[EPP_INDEX_BALANCE_PERFORMANCE] = AMD_CPPC_EPP_BALANCE_PERFORMANCE,
|
||||
[EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE,
|
||||
[EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE,
|
||||
};
|
||||
|
||||
static inline int get_mode_idx_from_str(const char *str, size_t size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i < AMD_PSTATE_MAX; i++) {
|
||||
if (!strncmp(str, amd_pstate_mode_string[i], size))
|
||||
return i;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static DEFINE_MUTEX(amd_pstate_limits_lock);
|
||||
static DEFINE_MUTEX(amd_pstate_driver_lock);
|
||||
|
||||
static s16 amd_pstate_get_epp(struct amd_cpudata *cpudata, u64 cppc_req_cached)
|
||||
{
|
||||
u64 epp;
|
||||
int ret;
|
||||
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
if (!cppc_req_cached) {
|
||||
epp = rdmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ,
|
||||
&cppc_req_cached);
|
||||
if (epp)
|
||||
return epp;
|
||||
}
|
||||
epp = (cppc_req_cached >> 24) & 0xFF;
|
||||
} else {
|
||||
ret = cppc_get_epp_perf(cpudata->cpu, &epp);
|
||||
if (ret < 0) {
|
||||
pr_debug("Could not retrieve energy perf value (%d)\n", ret);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return (s16)(epp & 0xff);
|
||||
}
|
||||
|
||||
static int amd_pstate_get_energy_pref_index(struct amd_cpudata *cpudata)
|
||||
{
|
||||
s16 epp;
|
||||
int index = -EINVAL;
|
||||
|
||||
epp = amd_pstate_get_epp(cpudata, 0);
|
||||
if (epp < 0)
|
||||
return epp;
|
||||
|
||||
switch (epp) {
|
||||
case AMD_CPPC_EPP_PERFORMANCE:
|
||||
index = EPP_INDEX_PERFORMANCE;
|
||||
break;
|
||||
case AMD_CPPC_EPP_BALANCE_PERFORMANCE:
|
||||
index = EPP_INDEX_BALANCE_PERFORMANCE;
|
||||
break;
|
||||
case AMD_CPPC_EPP_BALANCE_POWERSAVE:
|
||||
index = EPP_INDEX_BALANCE_POWERSAVE;
|
||||
break;
|
||||
case AMD_CPPC_EPP_POWERSAVE:
|
||||
index = EPP_INDEX_POWERSAVE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static int amd_pstate_set_epp(struct amd_cpudata *cpudata, u32 epp)
|
||||
{
|
||||
int ret;
|
||||
struct cppc_perf_ctrls perf_ctrls;
|
||||
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
u64 value = READ_ONCE(cpudata->cppc_req_cached);
|
||||
|
||||
value &= ~GENMASK_ULL(31, 24);
|
||||
value |= (u64)epp << 24;
|
||||
WRITE_ONCE(cpudata->cppc_req_cached, value);
|
||||
|
||||
ret = wrmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, value);
|
||||
if (!ret)
|
||||
cpudata->epp_cached = epp;
|
||||
} else {
|
||||
perf_ctrls.energy_perf = epp;
|
||||
ret = cppc_set_epp_perf(cpudata->cpu, &perf_ctrls, 1);
|
||||
if (ret) {
|
||||
pr_debug("failed to set energy perf value (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
cpudata->epp_cached = epp;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amd_pstate_set_energy_pref_index(struct amd_cpudata *cpudata,
|
||||
int pref_index)
|
||||
{
|
||||
int epp = -EINVAL;
|
||||
int ret;
|
||||
|
||||
if (!pref_index) {
|
||||
pr_debug("EPP pref_index is invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (epp == -EINVAL)
|
||||
epp = epp_values[pref_index];
|
||||
|
||||
if (epp > 0 && cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) {
|
||||
pr_debug("EPP cannot be set under performance policy\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = amd_pstate_set_epp(cpudata, epp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int pstate_enable(bool enable)
|
||||
{
|
||||
@@ -70,11 +233,21 @@ static inline int pstate_enable(bool enable)
|
||||
static int cppc_enable(bool enable)
|
||||
{
|
||||
int cpu, ret = 0;
|
||||
struct cppc_perf_ctrls perf_ctrls;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
ret = cppc_set_enable(cpu, enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable autonomous mode for EPP */
|
||||
if (cppc_state == AMD_PSTATE_ACTIVE) {
|
||||
/* Set desired perf as zero to allow EPP firmware control */
|
||||
perf_ctrls.desired_perf = 0;
|
||||
ret = cppc_set_perf(cpu, &perf_ctrls);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -418,7 +591,7 @@ static void amd_pstate_boost_init(struct amd_cpudata *cpudata)
|
||||
return;
|
||||
|
||||
cpudata->boost_supported = true;
|
||||
amd_pstate_driver.boost_enabled = true;
|
||||
current_pstate_driver->boost_enabled = true;
|
||||
}
|
||||
|
||||
static void amd_perf_ctl_reset(unsigned int cpu)
|
||||
@@ -501,6 +674,8 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy)
|
||||
policy->driver_data = cpudata;
|
||||
|
||||
amd_pstate_boost_init(cpudata);
|
||||
if (!current_pstate_driver->adjust_perf)
|
||||
current_pstate_driver->adjust_perf = amd_pstate_adjust_perf;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -561,7 +736,7 @@ static ssize_t show_amd_pstate_max_freq(struct cpufreq_policy *policy,
|
||||
if (max_freq < 0)
|
||||
return max_freq;
|
||||
|
||||
return sprintf(&buf[0], "%u\n", max_freq);
|
||||
return sysfs_emit(buf, "%u\n", max_freq);
|
||||
}
|
||||
|
||||
static ssize_t show_amd_pstate_lowest_nonlinear_freq(struct cpufreq_policy *policy,
|
||||
@@ -574,7 +749,7 @@ static ssize_t show_amd_pstate_lowest_nonlinear_freq(struct cpufreq_policy *poli
|
||||
if (freq < 0)
|
||||
return freq;
|
||||
|
||||
return sprintf(&buf[0], "%u\n", freq);
|
||||
return sysfs_emit(buf, "%u\n", freq);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -589,13 +764,151 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy,
|
||||
|
||||
perf = READ_ONCE(cpudata->highest_perf);
|
||||
|
||||
return sprintf(&buf[0], "%u\n", perf);
|
||||
return sysfs_emit(buf, "%u\n", perf);
|
||||
}
|
||||
|
||||
static ssize_t show_energy_performance_available_preferences(
|
||||
struct cpufreq_policy *policy, char *buf)
|
||||
{
|
||||
int i = 0;
|
||||
int offset = 0;
|
||||
|
||||
while (energy_perf_strings[i] != NULL)
|
||||
offset += sysfs_emit_at(buf, offset, "%s ", energy_perf_strings[i++]);
|
||||
|
||||
sysfs_emit_at(buf, offset, "\n");
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static ssize_t store_energy_performance_preference(
|
||||
struct cpufreq_policy *policy, const char *buf, size_t count)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
char str_preference[21];
|
||||
ssize_t ret;
|
||||
|
||||
ret = sscanf(buf, "%20s", str_preference);
|
||||
if (ret != 1)
|
||||
return -EINVAL;
|
||||
|
||||
ret = match_string(energy_perf_strings, -1, str_preference);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&amd_pstate_limits_lock);
|
||||
ret = amd_pstate_set_energy_pref_index(cpudata, ret);
|
||||
mutex_unlock(&amd_pstate_limits_lock);
|
||||
|
||||
return ret ?: count;
|
||||
}
|
||||
|
||||
static ssize_t show_energy_performance_preference(
|
||||
struct cpufreq_policy *policy, char *buf)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
int preference;
|
||||
|
||||
preference = amd_pstate_get_energy_pref_index(cpudata);
|
||||
if (preference < 0)
|
||||
return preference;
|
||||
|
||||
return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]);
|
||||
}
|
||||
|
||||
static ssize_t amd_pstate_show_status(char *buf)
|
||||
{
|
||||
if (!current_pstate_driver)
|
||||
return sysfs_emit(buf, "disable\n");
|
||||
|
||||
return sysfs_emit(buf, "%s\n", amd_pstate_mode_string[cppc_state]);
|
||||
}
|
||||
|
||||
static void amd_pstate_driver_cleanup(void)
|
||||
{
|
||||
current_pstate_driver = NULL;
|
||||
}
|
||||
|
||||
static int amd_pstate_update_status(const char *buf, size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
int mode_idx;
|
||||
|
||||
if (size > 7 || size < 6)
|
||||
return -EINVAL;
|
||||
mode_idx = get_mode_idx_from_str(buf, size);
|
||||
|
||||
switch(mode_idx) {
|
||||
case AMD_PSTATE_DISABLE:
|
||||
if (!current_pstate_driver)
|
||||
return -EINVAL;
|
||||
if (cppc_state == AMD_PSTATE_ACTIVE)
|
||||
return -EBUSY;
|
||||
cpufreq_unregister_driver(current_pstate_driver);
|
||||
amd_pstate_driver_cleanup();
|
||||
break;
|
||||
case AMD_PSTATE_PASSIVE:
|
||||
if (current_pstate_driver) {
|
||||
if (current_pstate_driver == &amd_pstate_driver)
|
||||
return 0;
|
||||
cpufreq_unregister_driver(current_pstate_driver);
|
||||
cppc_state = AMD_PSTATE_PASSIVE;
|
||||
current_pstate_driver = &amd_pstate_driver;
|
||||
}
|
||||
|
||||
ret = cpufreq_register_driver(current_pstate_driver);
|
||||
break;
|
||||
case AMD_PSTATE_ACTIVE:
|
||||
if (current_pstate_driver) {
|
||||
if (current_pstate_driver == &amd_pstate_epp_driver)
|
||||
return 0;
|
||||
cpufreq_unregister_driver(current_pstate_driver);
|
||||
current_pstate_driver = &amd_pstate_epp_driver;
|
||||
cppc_state = AMD_PSTATE_ACTIVE;
|
||||
}
|
||||
|
||||
ret = cpufreq_register_driver(current_pstate_driver);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t show_status(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&amd_pstate_driver_lock);
|
||||
ret = amd_pstate_show_status(buf);
|
||||
mutex_unlock(&amd_pstate_driver_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t store_status(struct kobject *a, struct kobj_attribute *b,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
char *p = memchr(buf, '\n', count);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&amd_pstate_driver_lock);
|
||||
ret = amd_pstate_update_status(buf, p ? p - buf : count);
|
||||
mutex_unlock(&amd_pstate_driver_lock);
|
||||
|
||||
return ret < 0 ? ret : count;
|
||||
}
|
||||
|
||||
cpufreq_freq_attr_ro(amd_pstate_max_freq);
|
||||
cpufreq_freq_attr_ro(amd_pstate_lowest_nonlinear_freq);
|
||||
|
||||
cpufreq_freq_attr_ro(amd_pstate_highest_perf);
|
||||
cpufreq_freq_attr_rw(energy_performance_preference);
|
||||
cpufreq_freq_attr_ro(energy_performance_available_preferences);
|
||||
define_one_global_rw(status);
|
||||
|
||||
static struct freq_attr *amd_pstate_attr[] = {
|
||||
&amd_pstate_max_freq,
|
||||
@@ -604,6 +917,313 @@ static struct freq_attr *amd_pstate_attr[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct freq_attr *amd_pstate_epp_attr[] = {
|
||||
&amd_pstate_max_freq,
|
||||
&amd_pstate_lowest_nonlinear_freq,
|
||||
&amd_pstate_highest_perf,
|
||||
&energy_performance_preference,
|
||||
&energy_performance_available_preferences,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute *pstate_global_attributes[] = {
|
||||
&status.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group amd_pstate_global_attr_group = {
|
||||
.attrs = pstate_global_attributes,
|
||||
};
|
||||
|
||||
static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy)
|
||||
{
|
||||
int min_freq, max_freq, nominal_freq, lowest_nonlinear_freq, ret;
|
||||
struct amd_cpudata *cpudata;
|
||||
struct device *dev;
|
||||
u64 value;
|
||||
|
||||
/*
|
||||
* Resetting PERF_CTL_MSR will put the CPU in P0 frequency,
|
||||
* which is ideal for initialization process.
|
||||
*/
|
||||
amd_perf_ctl_reset(policy->cpu);
|
||||
dev = get_cpu_device(policy->cpu);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
cpudata = kzalloc(sizeof(*cpudata), GFP_KERNEL);
|
||||
if (!cpudata)
|
||||
return -ENOMEM;
|
||||
|
||||
cpudata->cpu = policy->cpu;
|
||||
cpudata->epp_policy = 0;
|
||||
|
||||
ret = amd_pstate_init_perf(cpudata);
|
||||
if (ret)
|
||||
goto free_cpudata1;
|
||||
|
||||
min_freq = amd_get_min_freq(cpudata);
|
||||
max_freq = amd_get_max_freq(cpudata);
|
||||
nominal_freq = amd_get_nominal_freq(cpudata);
|
||||
lowest_nonlinear_freq = amd_get_lowest_nonlinear_freq(cpudata);
|
||||
if (min_freq < 0 || max_freq < 0 || min_freq > max_freq) {
|
||||
dev_err(dev, "min_freq(%d) or max_freq(%d) value is incorrect\n",
|
||||
min_freq, max_freq);
|
||||
ret = -EINVAL;
|
||||
goto free_cpudata1;
|
||||
}
|
||||
|
||||
policy->cpuinfo.min_freq = min_freq;
|
||||
policy->cpuinfo.max_freq = max_freq;
|
||||
/* It will be updated by governor */
|
||||
policy->cur = policy->cpuinfo.min_freq;
|
||||
|
||||
/* Initial processor data capability frequencies */
|
||||
cpudata->max_freq = max_freq;
|
||||
cpudata->min_freq = min_freq;
|
||||
cpudata->nominal_freq = nominal_freq;
|
||||
cpudata->lowest_nonlinear_freq = lowest_nonlinear_freq;
|
||||
|
||||
policy->driver_data = cpudata;
|
||||
|
||||
cpudata->epp_cached = amd_pstate_get_epp(cpudata, 0);
|
||||
|
||||
policy->min = policy->cpuinfo.min_freq;
|
||||
policy->max = policy->cpuinfo.max_freq;
|
||||
|
||||
/*
|
||||
* Set the policy to powersave to provide a valid fallback value in case
|
||||
* the default cpufreq governor is neither powersave nor performance.
|
||||
*/
|
||||
policy->policy = CPUFREQ_POLICY_POWERSAVE;
|
||||
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
policy->fast_switch_possible = true;
|
||||
ret = rdmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
WRITE_ONCE(cpudata->cppc_req_cached, value);
|
||||
|
||||
ret = rdmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
WRITE_ONCE(cpudata->cppc_cap1_cached, value);
|
||||
}
|
||||
amd_pstate_boost_init(cpudata);
|
||||
|
||||
return 0;
|
||||
|
||||
free_cpudata1:
|
||||
kfree(cpudata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_cpu_exit(struct cpufreq_policy *policy)
|
||||
{
|
||||
pr_debug("CPU %d exiting\n", policy->cpu);
|
||||
policy->fast_switch_possible = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_pstate_epp_init(unsigned int cpu)
|
||||
{
|
||||
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
u32 max_perf, min_perf;
|
||||
u64 value;
|
||||
s16 epp;
|
||||
|
||||
max_perf = READ_ONCE(cpudata->highest_perf);
|
||||
min_perf = READ_ONCE(cpudata->lowest_perf);
|
||||
|
||||
value = READ_ONCE(cpudata->cppc_req_cached);
|
||||
|
||||
if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE)
|
||||
min_perf = max_perf;
|
||||
|
||||
/* Initial min/max values for CPPC Performance Controls Register */
|
||||
value &= ~AMD_CPPC_MIN_PERF(~0L);
|
||||
value |= AMD_CPPC_MIN_PERF(min_perf);
|
||||
|
||||
value &= ~AMD_CPPC_MAX_PERF(~0L);
|
||||
value |= AMD_CPPC_MAX_PERF(max_perf);
|
||||
|
||||
/* CPPC EPP feature require to set zero to the desire perf bit */
|
||||
value &= ~AMD_CPPC_DES_PERF(~0L);
|
||||
value |= AMD_CPPC_DES_PERF(0);
|
||||
|
||||
if (cpudata->epp_policy == cpudata->policy)
|
||||
goto skip_epp;
|
||||
|
||||
cpudata->epp_policy = cpudata->policy;
|
||||
|
||||
/* Get BIOS pre-defined epp value */
|
||||
epp = amd_pstate_get_epp(cpudata, value);
|
||||
if (epp < 0) {
|
||||
/**
|
||||
* This return value can only be negative for shared_memory
|
||||
* systems where EPP register read/write not supported.
|
||||
*/
|
||||
goto skip_epp;
|
||||
}
|
||||
|
||||
if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE)
|
||||
epp = 0;
|
||||
|
||||
/* Set initial EPP value */
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
value &= ~GENMASK_ULL(31, 24);
|
||||
value |= (u64)epp << 24;
|
||||
}
|
||||
|
||||
WRITE_ONCE(cpudata->cppc_req_cached, value);
|
||||
amd_pstate_set_epp(cpudata, epp);
|
||||
skip_epp:
|
||||
cpufreq_cpu_put(policy);
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
|
||||
if (!policy->cpuinfo.max_freq)
|
||||
return -ENODEV;
|
||||
|
||||
pr_debug("set_policy: cpuinfo.max %u policy->max %u\n",
|
||||
policy->cpuinfo.max_freq, policy->max);
|
||||
|
||||
cpudata->policy = policy->policy;
|
||||
|
||||
amd_pstate_epp_init(policy->cpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_pstate_epp_reenable(struct amd_cpudata *cpudata)
|
||||
{
|
||||
struct cppc_perf_ctrls perf_ctrls;
|
||||
u64 value, max_perf;
|
||||
int ret;
|
||||
|
||||
ret = amd_pstate_enable(true);
|
||||
if (ret)
|
||||
pr_err("failed to enable amd pstate during resume, return %d\n", ret);
|
||||
|
||||
value = READ_ONCE(cpudata->cppc_req_cached);
|
||||
max_perf = READ_ONCE(cpudata->highest_perf);
|
||||
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
wrmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, value);
|
||||
} else {
|
||||
perf_ctrls.max_perf = max_perf;
|
||||
perf_ctrls.energy_perf = AMD_CPPC_ENERGY_PERF_PREF(cpudata->epp_cached);
|
||||
cppc_set_perf(cpudata->cpu, &perf_ctrls);
|
||||
}
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_cpu_online(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
|
||||
pr_debug("AMD CPU Core %d going online\n", cpudata->cpu);
|
||||
|
||||
if (cppc_state == AMD_PSTATE_ACTIVE) {
|
||||
amd_pstate_epp_reenable(cpudata);
|
||||
cpudata->suspended = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_pstate_epp_offline(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
struct cppc_perf_ctrls perf_ctrls;
|
||||
int min_perf;
|
||||
u64 value;
|
||||
|
||||
min_perf = READ_ONCE(cpudata->lowest_perf);
|
||||
value = READ_ONCE(cpudata->cppc_req_cached);
|
||||
|
||||
mutex_lock(&amd_pstate_limits_lock);
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
cpudata->epp_policy = CPUFREQ_POLICY_UNKNOWN;
|
||||
|
||||
/* Set max perf same as min perf */
|
||||
value &= ~AMD_CPPC_MAX_PERF(~0L);
|
||||
value |= AMD_CPPC_MAX_PERF(min_perf);
|
||||
value &= ~AMD_CPPC_MIN_PERF(~0L);
|
||||
value |= AMD_CPPC_MIN_PERF(min_perf);
|
||||
wrmsrl_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, value);
|
||||
} else {
|
||||
perf_ctrls.desired_perf = 0;
|
||||
perf_ctrls.max_perf = min_perf;
|
||||
perf_ctrls.energy_perf = AMD_CPPC_ENERGY_PERF_PREF(HWP_EPP_BALANCE_POWERSAVE);
|
||||
cppc_set_perf(cpudata->cpu, &perf_ctrls);
|
||||
}
|
||||
mutex_unlock(&amd_pstate_limits_lock);
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_cpu_offline(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
|
||||
pr_debug("AMD CPU Core %d going offline\n", cpudata->cpu);
|
||||
|
||||
if (cpudata->suspended)
|
||||
return 0;
|
||||
|
||||
if (cppc_state == AMD_PSTATE_ACTIVE)
|
||||
amd_pstate_epp_offline(policy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_verify_policy(struct cpufreq_policy_data *policy)
|
||||
{
|
||||
cpufreq_verify_within_cpu_limits(policy);
|
||||
pr_debug("policy_max =%d, policy_min=%d\n", policy->max, policy->min);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_suspend(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
int ret;
|
||||
|
||||
/* avoid suspending when EPP is not enabled */
|
||||
if (cppc_state != AMD_PSTATE_ACTIVE)
|
||||
return 0;
|
||||
|
||||
/* set this flag to avoid setting core offline*/
|
||||
cpudata->suspended = true;
|
||||
|
||||
/* disable CPPC in lowlevel firmware */
|
||||
ret = amd_pstate_enable(false);
|
||||
if (ret)
|
||||
pr_err("failed to suspend, return %d\n", ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_pstate_epp_resume(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct amd_cpudata *cpudata = policy->driver_data;
|
||||
|
||||
if (cpudata->suspended) {
|
||||
mutex_lock(&amd_pstate_limits_lock);
|
||||
|
||||
/* enable amd pstate from suspend state*/
|
||||
amd_pstate_epp_reenable(cpudata);
|
||||
|
||||
mutex_unlock(&amd_pstate_limits_lock);
|
||||
|
||||
cpudata->suspended = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct cpufreq_driver amd_pstate_driver = {
|
||||
.flags = CPUFREQ_CONST_LOOPS | CPUFREQ_NEED_UPDATE_LIMITS,
|
||||
.verify = amd_pstate_verify,
|
||||
@@ -617,6 +1237,20 @@ static struct cpufreq_driver amd_pstate_driver = {
|
||||
.attr = amd_pstate_attr,
|
||||
};
|
||||
|
||||
static struct cpufreq_driver amd_pstate_epp_driver = {
|
||||
.flags = CPUFREQ_CONST_LOOPS,
|
||||
.verify = amd_pstate_epp_verify_policy,
|
||||
.setpolicy = amd_pstate_epp_set_policy,
|
||||
.init = amd_pstate_epp_cpu_init,
|
||||
.exit = amd_pstate_epp_cpu_exit,
|
||||
.offline = amd_pstate_epp_cpu_offline,
|
||||
.online = amd_pstate_epp_cpu_online,
|
||||
.suspend = amd_pstate_epp_suspend,
|
||||
.resume = amd_pstate_epp_resume,
|
||||
.name = "amd_pstate_epp",
|
||||
.attr = amd_pstate_epp_attr,
|
||||
};
|
||||
|
||||
static int __init amd_pstate_init(void)
|
||||
{
|
||||
int ret;
|
||||
@@ -626,10 +1260,10 @@ static int __init amd_pstate_init(void)
|
||||
/*
|
||||
* by default the pstate driver is disabled to load
|
||||
* enable the amd_pstate passive mode driver explicitly
|
||||
* with amd_pstate=passive in kernel command line
|
||||
* with amd_pstate=passive or other modes in kernel command line
|
||||
*/
|
||||
if (!cppc_load) {
|
||||
pr_debug("driver load is disabled, boot with amd_pstate=passive to enable this\n");
|
||||
if (cppc_state == AMD_PSTATE_DISABLE) {
|
||||
pr_debug("driver load is disabled, boot with specific mode to enable this\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@@ -645,7 +1279,8 @@ static int __init amd_pstate_init(void)
|
||||
/* capability check */
|
||||
if (boot_cpu_has(X86_FEATURE_CPPC)) {
|
||||
pr_debug("AMD CPPC MSR based functionality is supported\n");
|
||||
amd_pstate_driver.adjust_perf = amd_pstate_adjust_perf;
|
||||
if (cppc_state == AMD_PSTATE_PASSIVE)
|
||||
current_pstate_driver->adjust_perf = amd_pstate_adjust_perf;
|
||||
} else {
|
||||
pr_debug("AMD CPPC shared memory based functionality is supported\n");
|
||||
static_call_update(amd_pstate_enable, cppc_enable);
|
||||
@@ -656,31 +1291,63 @@ static int __init amd_pstate_init(void)
|
||||
/* enable amd pstate feature */
|
||||
ret = amd_pstate_enable(true);
|
||||
if (ret) {
|
||||
pr_err("failed to enable amd-pstate with return %d\n", ret);
|
||||
pr_err("failed to enable with return %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = cpufreq_register_driver(&amd_pstate_driver);
|
||||
ret = cpufreq_register_driver(current_pstate_driver);
|
||||
if (ret)
|
||||
pr_err("failed to register amd_pstate_driver with return %d\n",
|
||||
ret);
|
||||
pr_err("failed to register with return %d\n", ret);
|
||||
|
||||
amd_pstate_kobj = kobject_create_and_add("amd_pstate", &cpu_subsys.dev_root->kobj);
|
||||
if (!amd_pstate_kobj) {
|
||||
ret = -EINVAL;
|
||||
pr_err("global sysfs registration failed.\n");
|
||||
goto kobject_free;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(amd_pstate_kobj, &amd_pstate_global_attr_group);
|
||||
if (ret) {
|
||||
pr_err("sysfs attribute export failed with error %d.\n", ret);
|
||||
goto global_attr_free;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
global_attr_free:
|
||||
kobject_put(amd_pstate_kobj);
|
||||
kobject_free:
|
||||
cpufreq_unregister_driver(current_pstate_driver);
|
||||
return ret;
|
||||
}
|
||||
device_initcall(amd_pstate_init);
|
||||
|
||||
static int __init amd_pstate_param(char *str)
|
||||
{
|
||||
size_t size;
|
||||
int mode_idx;
|
||||
|
||||
if (!str)
|
||||
return -EINVAL;
|
||||
|
||||
if (!strcmp(str, "disable")) {
|
||||
cppc_load = 0;
|
||||
pr_info("driver is explicitly disabled\n");
|
||||
} else if (!strcmp(str, "passive"))
|
||||
cppc_load = 1;
|
||||
size = strlen(str);
|
||||
mode_idx = get_mode_idx_from_str(str, size);
|
||||
|
||||
return 0;
|
||||
if (mode_idx >= AMD_PSTATE_DISABLE && mode_idx < AMD_PSTATE_MAX) {
|
||||
cppc_state = mode_idx;
|
||||
if (cppc_state == AMD_PSTATE_DISABLE)
|
||||
pr_info("driver is explicitly disabled\n");
|
||||
|
||||
if (cppc_state == AMD_PSTATE_ACTIVE)
|
||||
current_pstate_driver = &amd_pstate_epp_driver;
|
||||
|
||||
if (cppc_state == AMD_PSTATE_PASSIVE)
|
||||
current_pstate_driver = &amd_pstate_driver;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
early_param("amd_pstate", amd_pstate_param);
|
||||
|
||||
|
||||
@@ -751,10 +751,7 @@ static int brcm_avs_cpufreq_probe(struct platform_device *pdev)
|
||||
|
||||
static int brcm_avs_cpufreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = cpufreq_unregister_driver(&brcm_avs_driver);
|
||||
WARN_ON(ret);
|
||||
cpufreq_unregister_driver(&brcm_avs_driver);
|
||||
|
||||
brcm_avs_prepare_uninit(pdev);
|
||||
|
||||
|
||||
@@ -1004,7 +1004,7 @@ static const struct sysfs_ops sysfs_ops = {
|
||||
.store = store,
|
||||
};
|
||||
|
||||
static struct kobj_type ktype_cpufreq = {
|
||||
static const struct kobj_type ktype_cpufreq = {
|
||||
.sysfs_ops = &sysfs_ops,
|
||||
.default_groups = cpufreq_groups,
|
||||
.release = cpufreq_sysfs_release,
|
||||
@@ -2917,12 +2917,12 @@ EXPORT_SYMBOL_GPL(cpufreq_register_driver);
|
||||
* Returns zero if successful, and -EINVAL if the cpufreq_driver is
|
||||
* currently not initialised.
|
||||
*/
|
||||
int cpufreq_unregister_driver(struct cpufreq_driver *driver)
|
||||
void cpufreq_unregister_driver(struct cpufreq_driver *driver)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!cpufreq_driver || (driver != cpufreq_driver))
|
||||
return -EINVAL;
|
||||
if (WARN_ON(!cpufreq_driver || (driver != cpufreq_driver)))
|
||||
return;
|
||||
|
||||
pr_debug("unregistering driver %s\n", driver->name);
|
||||
|
||||
@@ -2939,8 +2939,6 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver)
|
||||
|
||||
write_unlock_irqrestore(&cpufreq_driver_lock, flags);
|
||||
cpus_read_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpufreq_unregister_driver);
|
||||
|
||||
|
||||
@@ -133,12 +133,14 @@ static int __init davinci_cpufreq_probe(struct platform_device *pdev)
|
||||
|
||||
static int __exit davinci_cpufreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
cpufreq_unregister_driver(&davinci_driver);
|
||||
|
||||
clk_put(cpufreq.armclk);
|
||||
|
||||
if (cpufreq.asyncclk)
|
||||
clk_put(cpufreq.asyncclk);
|
||||
|
||||
return cpufreq_unregister_driver(&davinci_driver);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver davinci_cpufreq_driver = {
|
||||
|
||||
@@ -452,20 +452,6 @@ static void intel_pstate_init_acpi_perf_limits(struct cpufreq_policy *policy)
|
||||
(u32) cpu->acpi_perf_data.states[i].control);
|
||||
}
|
||||
|
||||
/*
|
||||
* The _PSS table doesn't contain whole turbo frequency range.
|
||||
* This just contains +1 MHZ above the max non turbo frequency,
|
||||
* with control value corresponding to max turbo ratio. But
|
||||
* when cpufreq set policy is called, it will call with this
|
||||
* max frequency, which will cause a reduced performance as
|
||||
* this driver uses real max turbo frequency as the max
|
||||
* frequency. So correct this frequency in _PSS table to
|
||||
* correct max turbo frequency based on the turbo state.
|
||||
* Also need to convert to MHz as _PSS freq is in MHz.
|
||||
*/
|
||||
if (!global.turbo_disabled)
|
||||
cpu->acpi_perf_data.states[0].core_frequency =
|
||||
policy->cpuinfo.max_freq / 1000;
|
||||
cpu->valid_pss_table = true;
|
||||
pr_debug("_PPC limits will be enforced\n");
|
||||
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* CPU Frequency Scaling for Loongson 1 SoC
|
||||
*
|
||||
* Copyright (C) 2014-2016 Zhang, Keguang <keguang.zhang@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public
|
||||
* License version 2. This program is licensed "as is" without any
|
||||
* warranty of any kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <cpufreq.h>
|
||||
#include <loongson1.h>
|
||||
|
||||
struct ls1x_cpufreq {
|
||||
struct device *dev;
|
||||
struct clk *clk; /* CPU clk */
|
||||
struct clk *mux_clk; /* MUX of CPU clk */
|
||||
struct clk *pll_clk; /* PLL clk */
|
||||
struct clk *osc_clk; /* OSC clk */
|
||||
unsigned int max_freq;
|
||||
unsigned int min_freq;
|
||||
};
|
||||
|
||||
static struct ls1x_cpufreq *cpufreq;
|
||||
|
||||
static int ls1x_cpufreq_notifier(struct notifier_block *nb,
|
||||
unsigned long val, void *data)
|
||||
{
|
||||
if (val == CPUFREQ_POSTCHANGE)
|
||||
current_cpu_data.udelay_val = loops_per_jiffy;
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block ls1x_cpufreq_notifier_block = {
|
||||
.notifier_call = ls1x_cpufreq_notifier
|
||||
};
|
||||
|
||||
static int ls1x_cpufreq_target(struct cpufreq_policy *policy,
|
||||
unsigned int index)
|
||||
{
|
||||
struct device *cpu_dev = get_cpu_device(policy->cpu);
|
||||
unsigned int old_freq, new_freq;
|
||||
|
||||
old_freq = policy->cur;
|
||||
new_freq = policy->freq_table[index].frequency;
|
||||
|
||||
/*
|
||||
* The procedure of reconfiguring CPU clk is as below.
|
||||
*
|
||||
* - Reparent CPU clk to OSC clk
|
||||
* - Reset CPU clock (very important)
|
||||
* - Reconfigure CPU DIV
|
||||
* - Reparent CPU clk back to CPU DIV clk
|
||||
*/
|
||||
|
||||
clk_set_parent(policy->clk, cpufreq->osc_clk);
|
||||
__raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) | RST_CPU_EN | RST_CPU,
|
||||
LS1X_CLK_PLL_DIV);
|
||||
__raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) & ~(RST_CPU_EN | RST_CPU),
|
||||
LS1X_CLK_PLL_DIV);
|
||||
clk_set_rate(cpufreq->mux_clk, new_freq * 1000);
|
||||
clk_set_parent(policy->clk, cpufreq->mux_clk);
|
||||
dev_dbg(cpu_dev, "%u KHz --> %u KHz\n", old_freq, new_freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls1x_cpufreq_init(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct device *cpu_dev = get_cpu_device(policy->cpu);
|
||||
struct cpufreq_frequency_table *freq_tbl;
|
||||
unsigned int pll_freq, freq;
|
||||
int steps, i;
|
||||
|
||||
pll_freq = clk_get_rate(cpufreq->pll_clk) / 1000;
|
||||
|
||||
steps = 1 << DIV_CPU_WIDTH;
|
||||
freq_tbl = kcalloc(steps, sizeof(*freq_tbl), GFP_KERNEL);
|
||||
if (!freq_tbl)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < (steps - 1); i++) {
|
||||
freq = pll_freq / (i + 1);
|
||||
if ((freq < cpufreq->min_freq) || (freq > cpufreq->max_freq))
|
||||
freq_tbl[i].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
else
|
||||
freq_tbl[i].frequency = freq;
|
||||
dev_dbg(cpu_dev,
|
||||
"cpufreq table: index %d: frequency %d\n", i,
|
||||
freq_tbl[i].frequency);
|
||||
}
|
||||
freq_tbl[i].frequency = CPUFREQ_TABLE_END;
|
||||
|
||||
policy->clk = cpufreq->clk;
|
||||
cpufreq_generic_init(policy, freq_tbl, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls1x_cpufreq_exit(struct cpufreq_policy *policy)
|
||||
{
|
||||
kfree(policy->freq_table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct cpufreq_driver ls1x_cpufreq_driver = {
|
||||
.name = "cpufreq-ls1x",
|
||||
.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
|
||||
.verify = cpufreq_generic_frequency_table_verify,
|
||||
.target_index = ls1x_cpufreq_target,
|
||||
.get = cpufreq_generic_get,
|
||||
.init = ls1x_cpufreq_init,
|
||||
.exit = ls1x_cpufreq_exit,
|
||||
.attr = cpufreq_generic_attr,
|
||||
};
|
||||
|
||||
static int ls1x_cpufreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
cpufreq_unregister_notifier(&ls1x_cpufreq_notifier_block,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
cpufreq_unregister_driver(&ls1x_cpufreq_driver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls1x_cpufreq_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct plat_ls1x_cpufreq *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
if (!pdata || !pdata->clk_name || !pdata->osc_clk_name) {
|
||||
dev_err(&pdev->dev, "platform data missing\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cpufreq =
|
||||
devm_kzalloc(&pdev->dev, sizeof(struct ls1x_cpufreq), GFP_KERNEL);
|
||||
if (!cpufreq)
|
||||
return -ENOMEM;
|
||||
|
||||
cpufreq->dev = &pdev->dev;
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, pdata->clk_name);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get %s clock\n",
|
||||
pdata->clk_name);
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
cpufreq->clk = clk;
|
||||
|
||||
clk = clk_get_parent(clk);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get parent of %s clock\n",
|
||||
__clk_get_name(cpufreq->clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
cpufreq->mux_clk = clk;
|
||||
|
||||
clk = clk_get_parent(clk);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get parent of %s clock\n",
|
||||
__clk_get_name(cpufreq->mux_clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
cpufreq->pll_clk = clk;
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, pdata->osc_clk_name);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "unable to get %s clock\n",
|
||||
pdata->osc_clk_name);
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
cpufreq->osc_clk = clk;
|
||||
|
||||
cpufreq->max_freq = pdata->max_freq;
|
||||
cpufreq->min_freq = pdata->min_freq;
|
||||
|
||||
ret = cpufreq_register_driver(&ls1x_cpufreq_driver);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to register CPUFreq driver: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = cpufreq_register_notifier(&ls1x_cpufreq_notifier_block,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to register CPUFreq notifier: %d\n",ret);
|
||||
cpufreq_unregister_driver(&ls1x_cpufreq_driver);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver ls1x_cpufreq_platdrv = {
|
||||
.probe = ls1x_cpufreq_probe,
|
||||
.remove = ls1x_cpufreq_remove,
|
||||
.driver = {
|
||||
.name = "ls1x-cpufreq",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ls1x_cpufreq_platdrv);
|
||||
|
||||
MODULE_ALIAS("platform:ls1x-cpufreq");
|
||||
MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>");
|
||||
MODULE_DESCRIPTION("Loongson1 CPUFreq driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -317,13 +317,16 @@ static int mtk_cpufreq_hw_driver_probe(struct platform_device *pdev)
|
||||
|
||||
static int mtk_cpufreq_hw_driver_remove(struct platform_device *pdev)
|
||||
{
|
||||
return cpufreq_unregister_driver(&cpufreq_mtk_hw_driver);
|
||||
cpufreq_unregister_driver(&cpufreq_mtk_hw_driver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mtk_cpufreq_hw_match[] = {
|
||||
{ .compatible = "mediatek,cpufreq-hw", .data = &cpufreq_mtk_offsets },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mtk_cpufreq_hw_match);
|
||||
|
||||
static struct platform_driver mtk_cpufreq_hw_driver = {
|
||||
.probe = mtk_cpufreq_hw_driver_probe,
|
||||
|
||||
@@ -184,7 +184,9 @@ static int omap_cpufreq_probe(struct platform_device *pdev)
|
||||
|
||||
static int omap_cpufreq_remove(struct platform_device *pdev)
|
||||
{
|
||||
return cpufreq_unregister_driver(&omap_driver);
|
||||
cpufreq_unregister_driver(&omap_driver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap_cpufreq_platdrv = {
|
||||
|
||||
@@ -770,7 +770,9 @@ of_exit:
|
||||
|
||||
static int qcom_cpufreq_hw_driver_remove(struct platform_device *pdev)
|
||||
{
|
||||
return cpufreq_unregister_driver(&cpufreq_qcom_hw_driver);
|
||||
cpufreq_unregister_driver(&cpufreq_qcom_hw_driver);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver qcom_cpufreq_hw_driver = {
|
||||
|
||||
@@ -411,7 +411,8 @@ static int tegra194_cpufreq_set_target(struct cpufreq_policy *policy,
|
||||
|
||||
static struct cpufreq_driver tegra194_cpufreq_driver = {
|
||||
.name = "tegra194",
|
||||
.flags = CPUFREQ_CONST_LOOPS | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
|
||||
.flags = CPUFREQ_CONST_LOOPS | CPUFREQ_NEED_INITIAL_FREQ_CHECK |
|
||||
CPUFREQ_IS_COOLING_DEV,
|
||||
.verify = cpufreq_generic_frequency_table_verify,
|
||||
.target_index = tegra194_cpufreq_set_target,
|
||||
.get = tegra194_get_speed,
|
||||
|
||||
@@ -74,6 +74,7 @@ endmenu
|
||||
config HALTPOLL_CPUIDLE
|
||||
tristate "Halt poll cpuidle driver"
|
||||
depends on X86 && KVM_GUEST
|
||||
select CPU_IDLE_GOV_HALTPOLL
|
||||
default y
|
||||
help
|
||||
This option enables halt poll cpuidle driver, which allows to poll
|
||||
|
||||
@@ -24,6 +24,14 @@ config ARM_PSCI_CPUIDLE
|
||||
It provides an idle driver that is capable of detecting and
|
||||
managing idle states through the PSCI firmware interface.
|
||||
|
||||
The driver has limitations when used with PREEMPT_RT:
|
||||
- If the idle states are described with the non-hierarchical layout,
|
||||
all idle states are still available.
|
||||
|
||||
- If the idle states are described with the hierarchical layout,
|
||||
only the idle states defined per CPU are available, but not the ones
|
||||
being shared among a group of CPUs (aka cluster idle states).
|
||||
|
||||
config ARM_PSCI_CPUIDLE_DOMAIN
|
||||
bool "PSCI CPU idle Domain"
|
||||
depends on ARM_PSCI_CPUIDLE
|
||||
@@ -102,6 +110,7 @@ config ARM_MVEBU_V7_CPUIDLE
|
||||
config ARM_TEGRA_CPUIDLE
|
||||
bool "CPU Idle Driver for NVIDIA Tegra SoCs"
|
||||
depends on (ARCH_TEGRA || COMPILE_TEST) && !ARM64 && MMU
|
||||
depends on ARCH_SUSPEND_POSSIBLE
|
||||
select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP
|
||||
select ARM_CPU_SUSPEND
|
||||
help
|
||||
@@ -110,6 +119,7 @@ config ARM_TEGRA_CPUIDLE
|
||||
config ARM_QCOM_SPM_CPUIDLE
|
||||
bool "CPU Idle Driver for Qualcomm Subsystem Power Manager (SPM)"
|
||||
depends on (ARCH_QCOM || COMPILE_TEST) && !ARM64 && MMU
|
||||
depends on ARCH_SUSPEND_POSSIBLE
|
||||
select ARM_CPU_SUSPEND
|
||||
select CPU_IDLE_MULTIPLE_DRIVERS
|
||||
select DT_IDLE_STATES
|
||||
|
||||
@@ -32,7 +32,7 @@ static int default_enter_idle(struct cpuidle_device *dev,
|
||||
local_irq_enable();
|
||||
return index;
|
||||
}
|
||||
default_idle();
|
||||
arch_cpu_idle();
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,11 @@ static int psci_pd_init(struct device_node *np, bool use_osi)
|
||||
|
||||
pd->flags |= GENPD_FLAG_IRQ_SAFE | GENPD_FLAG_CPU_DOMAIN;
|
||||
|
||||
/* Allow power off when OSI has been successfully enabled. */
|
||||
if (use_osi)
|
||||
/*
|
||||
* Allow power off when OSI has been successfully enabled.
|
||||
* PREEMPT_RT is not yet ready to enter domain idle states.
|
||||
*/
|
||||
if (use_osi && !IS_ENABLED(CONFIG_PREEMPT_RT))
|
||||
pd->power_off = psci_pd_power_off;
|
||||
else
|
||||
pd->flags |= GENPD_FLAG_ALWAYS_ON;
|
||||
|
||||
@@ -227,6 +227,9 @@ static int psci_dt_cpu_init_topology(struct cpuidle_driver *drv,
|
||||
if (!psci_has_osi_support())
|
||||
return 0;
|
||||
|
||||
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
||||
return 0;
|
||||
|
||||
data->dev = psci_dt_attach_cpu(cpu);
|
||||
if (IS_ERR_OR_NULL(data->dev))
|
||||
return PTR_ERR_OR_ZERO(data->dev);
|
||||
|
||||
@@ -183,11 +183,15 @@ static void __cpuidle_driver_init(struct cpuidle_driver *drv)
|
||||
s->target_residency_ns = s->target_residency * NSEC_PER_USEC;
|
||||
else if (s->target_residency_ns < 0)
|
||||
s->target_residency_ns = 0;
|
||||
else
|
||||
s->target_residency = div_u64(s->target_residency_ns, NSEC_PER_USEC);
|
||||
|
||||
if (s->exit_latency > 0)
|
||||
s->exit_latency_ns = s->exit_latency * NSEC_PER_USEC;
|
||||
else if (s->exit_latency_ns < 0)
|
||||
s->exit_latency_ns = 0;
|
||||
else
|
||||
s->exit_latency = div_u64(s->exit_latency_ns, NSEC_PER_USEC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
/*
|
||||
* Timer events oriented CPU idle governor
|
||||
*
|
||||
* TEO governor:
|
||||
* Copyright (C) 2018 - 2021 Intel Corporation
|
||||
* Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
|
||||
*
|
||||
* Util-awareness mechanism:
|
||||
* Copyright (C) 2022 Arm Ltd.
|
||||
* Author: Kajetan Puchalski <kajetan.puchalski@arm.com>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -99,14 +104,55 @@
|
||||
* select the given idle state instead of the candidate one.
|
||||
*
|
||||
* 3. By default, select the candidate state.
|
||||
*
|
||||
* Util-awareness mechanism:
|
||||
*
|
||||
* The idea behind the util-awareness extension is that there are two distinct
|
||||
* scenarios for the CPU which should result in two different approaches to idle
|
||||
* state selection - utilized and not utilized.
|
||||
*
|
||||
* In this case, 'utilized' means that the average runqueue util of the CPU is
|
||||
* above a certain threshold.
|
||||
*
|
||||
* When the CPU is utilized while going into idle, more likely than not it will
|
||||
* be woken up to do more work soon and so a shallower idle state should be
|
||||
* selected to minimise latency and maximise performance. When the CPU is not
|
||||
* being utilized, the usual metrics-based approach to selecting the deepest
|
||||
* available idle state should be preferred to take advantage of the power
|
||||
* saving.
|
||||
*
|
||||
* In order to achieve this, the governor uses a utilization threshold.
|
||||
* The threshold is computed per-CPU as a percentage of the CPU's capacity
|
||||
* by bit shifting the capacity value. Based on testing, the shift of 6 (~1.56%)
|
||||
* seems to be getting the best results.
|
||||
*
|
||||
* Before selecting the next idle state, the governor compares the current CPU
|
||||
* util to the precomputed util threshold. If it's below, it defaults to the
|
||||
* TEO metrics mechanism. If it's above, the closest shallower idle state will
|
||||
* be selected instead, as long as is not a polling state.
|
||||
*/
|
||||
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/clock.h>
|
||||
#include <linux/sched/topology.h>
|
||||
#include <linux/tick.h>
|
||||
|
||||
/*
|
||||
* The number of bits to shift the CPU's capacity by in order to determine
|
||||
* the utilized threshold.
|
||||
*
|
||||
* 6 was chosen based on testing as the number that achieved the best balance
|
||||
* of power and performance on average.
|
||||
*
|
||||
* The resulting threshold is high enough to not be triggered by background
|
||||
* noise and low enough to react quickly when activity starts to ramp up.
|
||||
*/
|
||||
#define UTIL_THRESHOLD_SHIFT 6
|
||||
|
||||
|
||||
/*
|
||||
* The PULSE value is added to metrics when they grow and the DECAY_SHIFT value
|
||||
* is used for decreasing metrics on a regular basis.
|
||||
@@ -137,9 +183,11 @@ struct teo_bin {
|
||||
* @time_span_ns: Time between idle state selection and post-wakeup update.
|
||||
* @sleep_length_ns: Time till the closest timer event (at the selection time).
|
||||
* @state_bins: Idle state data bins for this CPU.
|
||||
* @total: Grand total of the "intercepts" and "hits" mertics for all bins.
|
||||
* @total: Grand total of the "intercepts" and "hits" metrics for all bins.
|
||||
* @next_recent_idx: Index of the next @recent_idx entry to update.
|
||||
* @recent_idx: Indices of bins corresponding to recent "intercepts".
|
||||
* @util_threshold: Threshold above which the CPU is considered utilized
|
||||
* @utilized: Whether the last sleep on the CPU happened while utilized
|
||||
*/
|
||||
struct teo_cpu {
|
||||
s64 time_span_ns;
|
||||
@@ -148,10 +196,29 @@ struct teo_cpu {
|
||||
unsigned int total;
|
||||
int next_recent_idx;
|
||||
int recent_idx[NR_RECENT];
|
||||
unsigned long util_threshold;
|
||||
bool utilized;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct teo_cpu, teo_cpus);
|
||||
|
||||
/**
|
||||
* teo_cpu_is_utilized - Check if the CPU's util is above the threshold
|
||||
* @cpu: Target CPU
|
||||
* @cpu_data: Governor CPU data for the target CPU
|
||||
*/
|
||||
#ifdef CONFIG_SMP
|
||||
static bool teo_cpu_is_utilized(int cpu, struct teo_cpu *cpu_data)
|
||||
{
|
||||
return sched_cpu_util(cpu) > cpu_data->util_threshold;
|
||||
}
|
||||
#else
|
||||
static bool teo_cpu_is_utilized(int cpu, struct teo_cpu *cpu_data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* teo_update - Update CPU metrics after wakeup.
|
||||
* @drv: cpuidle driver containing state data.
|
||||
@@ -258,15 +325,17 @@ static s64 teo_middle_of_bin(int idx, struct cpuidle_driver *drv)
|
||||
* @dev: Target CPU.
|
||||
* @state_idx: Index of the capping idle state.
|
||||
* @duration_ns: Idle duration value to match.
|
||||
* @no_poll: Don't consider polling states.
|
||||
*/
|
||||
static int teo_find_shallower_state(struct cpuidle_driver *drv,
|
||||
struct cpuidle_device *dev, int state_idx,
|
||||
s64 duration_ns)
|
||||
s64 duration_ns, bool no_poll)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = state_idx - 1; i >= 0; i--) {
|
||||
if (dev->states_usage[i].disable)
|
||||
if (dev->states_usage[i].disable ||
|
||||
(no_poll && drv->states[i].flags & CPUIDLE_FLAG_POLLING))
|
||||
continue;
|
||||
|
||||
state_idx = i;
|
||||
@@ -321,6 +390,22 @@ static int teo_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,
|
||||
goto end;
|
||||
}
|
||||
|
||||
cpu_data->utilized = teo_cpu_is_utilized(dev->cpu, cpu_data);
|
||||
/*
|
||||
* If the CPU is being utilized over the threshold and there are only 2
|
||||
* states to choose from, the metrics need not be considered, so choose
|
||||
* the shallowest non-polling state and exit.
|
||||
*/
|
||||
if (drv->state_count < 3 && cpu_data->utilized) {
|
||||
for (i = 0; i < drv->state_count; ++i) {
|
||||
if (!dev->states_usage[i].disable &&
|
||||
!(drv->states[i].flags & CPUIDLE_FLAG_POLLING)) {
|
||||
idx = i;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the deepest idle state whose target residency does not exceed
|
||||
* the current sleep length and the deepest idle state not deeper than
|
||||
@@ -452,6 +537,13 @@ static int teo_select(struct cpuidle_driver *drv, struct cpuidle_device *dev,
|
||||
if (idx > constraint_idx)
|
||||
idx = constraint_idx;
|
||||
|
||||
/*
|
||||
* If the CPU is being utilized over the threshold, choose a shallower
|
||||
* non-polling state to improve latency
|
||||
*/
|
||||
if (cpu_data->utilized)
|
||||
idx = teo_find_shallower_state(drv, dev, idx, duration_ns, true);
|
||||
|
||||
end:
|
||||
/*
|
||||
* Don't stop the tick if the selected state is a polling one or if the
|
||||
@@ -469,7 +561,7 @@ end:
|
||||
*/
|
||||
if (idx > idx0 &&
|
||||
drv->states[idx].target_residency_ns > delta_tick)
|
||||
idx = teo_find_shallower_state(drv, dev, idx, delta_tick);
|
||||
idx = teo_find_shallower_state(drv, dev, idx, delta_tick, false);
|
||||
}
|
||||
|
||||
return idx;
|
||||
@@ -508,9 +600,11 @@ static int teo_enable_device(struct cpuidle_driver *drv,
|
||||
struct cpuidle_device *dev)
|
||||
{
|
||||
struct teo_cpu *cpu_data = per_cpu_ptr(&teo_cpus, dev->cpu);
|
||||
unsigned long max_capacity = arch_scale_cpu_capacity(dev->cpu);
|
||||
int i;
|
||||
|
||||
memset(cpu_data, 0, sizeof(*cpu_data));
|
||||
cpu_data->util_threshold = max_capacity >> UTIL_THRESHOLD_SHIFT;
|
||||
|
||||
for (i = 0; i < NR_RECENT; i++)
|
||||
cpu_data->recent_idx[i] = -1;
|
||||
|
||||
@@ -200,7 +200,7 @@ static void cpuidle_sysfs_release(struct kobject *kobj)
|
||||
complete(&kdev->kobj_unregister);
|
||||
}
|
||||
|
||||
static struct kobj_type ktype_cpuidle = {
|
||||
static const struct kobj_type ktype_cpuidle = {
|
||||
.sysfs_ops = &cpuidle_sysfs_ops,
|
||||
.release = cpuidle_sysfs_release,
|
||||
};
|
||||
@@ -447,7 +447,7 @@ static void cpuidle_state_sysfs_release(struct kobject *kobj)
|
||||
complete(&state_obj->kobj_unregister);
|
||||
}
|
||||
|
||||
static struct kobj_type ktype_state_cpuidle = {
|
||||
static const struct kobj_type ktype_state_cpuidle = {
|
||||
.sysfs_ops = &cpuidle_state_sysfs_ops,
|
||||
.default_groups = cpuidle_state_default_groups,
|
||||
.release = cpuidle_state_sysfs_release,
|
||||
@@ -594,7 +594,7 @@ static struct attribute *cpuidle_driver_default_attrs[] = {
|
||||
};
|
||||
ATTRIBUTE_GROUPS(cpuidle_driver_default);
|
||||
|
||||
static struct kobj_type ktype_driver_cpuidle = {
|
||||
static const struct kobj_type ktype_driver_cpuidle = {
|
||||
.sysfs_ops = &cpuidle_driver_sysfs_ops,
|
||||
.default_groups = cpuidle_driver_default_groups,
|
||||
.release = cpuidle_driver_sysfs_release,
|
||||
|
||||
@@ -124,9 +124,8 @@ err_free_mem:
|
||||
for (j = 0; j < i; j++) {
|
||||
dma_free_coherent(dev, block_size, block[j].sgl,
|
||||
block[j].sgl_dma);
|
||||
memset(block + j, 0, sizeof(*block));
|
||||
}
|
||||
kfree(pool);
|
||||
kfree_sensitive(pool);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hisi_acc_create_sgl_pool);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
menuconfig DAX
|
||||
tristate "DAX: direct access to differentiated memory"
|
||||
select SRCU
|
||||
default m if NVDIMM_DAX
|
||||
|
||||
if DAX
|
||||
|
||||
@@ -45,7 +45,7 @@ struct gvt_firmware_header {
|
||||
u64 cfg_space_offset; /* offset in the file */
|
||||
u64 mmio_size;
|
||||
u64 mmio_offset; /* offset in the file */
|
||||
unsigned char data[1];
|
||||
unsigned char data[];
|
||||
};
|
||||
|
||||
#define dev_to_drm_minor(d) dev_get_drvdata((d))
|
||||
@@ -77,7 +77,7 @@ static int expose_firmware_sysfs(struct intel_gvt *gvt)
|
||||
unsigned long size, crc32_start;
|
||||
int ret;
|
||||
|
||||
size = sizeof(*h) + info->mmio_size + info->cfg_space_size;
|
||||
size = offsetof(struct gvt_firmware_header, data) + info->mmio_size + info->cfg_space_size;
|
||||
firmware = vzalloc(size);
|
||||
if (!firmware)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#define __NVIF_OUTP_H__
|
||||
#include <nvif/object.h>
|
||||
#include <nvif/if0012.h>
|
||||
#include <drm/display/drm_dp.h>
|
||||
struct nvif_disp;
|
||||
|
||||
struct nvif_outp {
|
||||
@@ -21,7 +22,7 @@ int nvif_outp_acquire_rgb_crt(struct nvif_outp *);
|
||||
int nvif_outp_acquire_tmds(struct nvif_outp *, int head,
|
||||
bool hdmi, u8 max_ac_packet, u8 rekey, u8 scdc, bool hda);
|
||||
int nvif_outp_acquire_lvds(struct nvif_outp *, bool dual, bool bpc8);
|
||||
int nvif_outp_acquire_dp(struct nvif_outp *, u8 dpcd[16],
|
||||
int nvif_outp_acquire_dp(struct nvif_outp *outp, u8 dpcd[DP_RECEIVER_CAP_SIZE],
|
||||
int link_nr, int link_bw, bool hda, bool mst);
|
||||
void nvif_outp_release(struct nvif_outp *);
|
||||
int nvif_outp_infoframe(struct nvif_outp *, u8 type, struct nvif_outp_infoframe_v0 *, u32 size);
|
||||
|
||||
@@ -127,7 +127,7 @@ nvif_outp_acquire(struct nvif_outp *outp, u8 proto, struct nvif_outp_acquire_v0
|
||||
}
|
||||
|
||||
int
|
||||
nvif_outp_acquire_dp(struct nvif_outp *outp, u8 dpcd[16],
|
||||
nvif_outp_acquire_dp(struct nvif_outp *outp, u8 dpcd[DP_RECEIVER_CAP_SIZE],
|
||||
int link_nr, int link_bw, bool hda, bool mst)
|
||||
{
|
||||
struct nvif_outp_acquire_v0 args;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
config STM
|
||||
tristate "System Trace Module devices"
|
||||
select CONFIGFS_FS
|
||||
select SRCU
|
||||
help
|
||||
A System Trace Module (STM) is a device exporting data in System
|
||||
Trace Protocol (STP) format as defined by MIPI STP standards.
|
||||
|
||||
@@ -1424,6 +1424,7 @@ static const struct x86_cpu_id intel_idle_ids[] __initconst = {
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, &idle_cpu_adl_l),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_N, &idle_cpu_adl_n),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, &idle_cpu_spr),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(EMERALDRAPIDS_X, &idle_cpu_spr),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNL, &idle_cpu_knl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNM, &idle_cpu_knl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, &idle_cpu_bxt),
|
||||
@@ -1859,6 +1860,7 @@ static void __init intel_idle_init_cstates_icpu(struct cpuidle_driver *drv)
|
||||
skx_idle_state_table_update();
|
||||
break;
|
||||
case INTEL_FAM6_SAPPHIRERAPIDS_X:
|
||||
case INTEL_FAM6_EMERALDRAPIDS_X:
|
||||
spr_idle_state_table_update();
|
||||
break;
|
||||
case INTEL_FAM6_ALDERLAKE:
|
||||
|
||||
@@ -384,7 +384,7 @@ config LS_EXTIRQ
|
||||
|
||||
config LS_SCFG_MSI
|
||||
def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE
|
||||
depends on PCI && PCI_MSI
|
||||
depends on PCI_MSI
|
||||
|
||||
config PARTITION_PERCPU
|
||||
bool
|
||||
@@ -653,6 +653,7 @@ config APPLE_AIC
|
||||
bool "Apple Interrupt Controller (AIC)"
|
||||
depends on ARM64
|
||||
depends on ARCH_APPLE || COMPILE_TEST
|
||||
select GENERIC_IRQ_IPI_MUX
|
||||
help
|
||||
Support for the Apple Interrupt Controller found on Apple Silicon SoCs,
|
||||
such as the M1.
|
||||
|
||||
@@ -199,21 +199,20 @@ static int alpine_msix_init_domains(struct alpine_msix_data *priv,
|
||||
}
|
||||
|
||||
gic_domain = irq_find_host(gic_node);
|
||||
of_node_put(gic_node);
|
||||
if (!gic_domain) {
|
||||
pr_err("Failed to find the GIC domain\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
middle_domain = irq_domain_add_tree(NULL,
|
||||
&alpine_msix_middle_domain_ops,
|
||||
priv);
|
||||
middle_domain = irq_domain_add_hierarchy(gic_domain, 0, 0, NULL,
|
||||
&alpine_msix_middle_domain_ops,
|
||||
priv);
|
||||
if (!middle_domain) {
|
||||
pr_err("Failed to create the MSIX middle domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
middle_domain->parent = gic_domain;
|
||||
|
||||
msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
&alpine_msix_domain_info,
|
||||
middle_domain);
|
||||
|
||||
@@ -292,7 +292,6 @@ struct aic_irq_chip {
|
||||
void __iomem *base;
|
||||
void __iomem *event;
|
||||
struct irq_domain *hw_domain;
|
||||
struct irq_domain *ipi_domain;
|
||||
struct {
|
||||
cpumask_t aff;
|
||||
} *fiq_aff[AIC_NR_FIQ];
|
||||
@@ -307,9 +306,6 @@ struct aic_irq_chip {
|
||||
|
||||
static DEFINE_PER_CPU(uint32_t, aic_fiq_unmasked);
|
||||
|
||||
static DEFINE_PER_CPU(atomic_t, aic_vipi_flag);
|
||||
static DEFINE_PER_CPU(atomic_t, aic_vipi_enable);
|
||||
|
||||
static struct aic_irq_chip *aic_irqc;
|
||||
|
||||
static void aic_handle_ipi(struct pt_regs *regs);
|
||||
@@ -751,98 +747,8 @@ static void aic_ipi_send_fast(int cpu)
|
||||
isb();
|
||||
}
|
||||
|
||||
static void aic_ipi_mask(struct irq_data *d)
|
||||
{
|
||||
u32 irq_bit = BIT(irqd_to_hwirq(d));
|
||||
|
||||
/* No specific ordering requirements needed here. */
|
||||
atomic_andnot(irq_bit, this_cpu_ptr(&aic_vipi_enable));
|
||||
}
|
||||
|
||||
static void aic_ipi_unmask(struct irq_data *d)
|
||||
{
|
||||
struct aic_irq_chip *ic = irq_data_get_irq_chip_data(d);
|
||||
u32 irq_bit = BIT(irqd_to_hwirq(d));
|
||||
|
||||
atomic_or(irq_bit, this_cpu_ptr(&aic_vipi_enable));
|
||||
|
||||
/*
|
||||
* The atomic_or() above must complete before the atomic_read()
|
||||
* below to avoid racing aic_ipi_send_mask().
|
||||
*/
|
||||
smp_mb__after_atomic();
|
||||
|
||||
/*
|
||||
* If a pending vIPI was unmasked, raise a HW IPI to ourselves.
|
||||
* No barriers needed here since this is a self-IPI.
|
||||
*/
|
||||
if (atomic_read(this_cpu_ptr(&aic_vipi_flag)) & irq_bit) {
|
||||
if (static_branch_likely(&use_fast_ipi))
|
||||
aic_ipi_send_fast(smp_processor_id());
|
||||
else
|
||||
aic_ic_write(ic, AIC_IPI_SEND, AIC_IPI_SEND_CPU(smp_processor_id()));
|
||||
}
|
||||
}
|
||||
|
||||
static void aic_ipi_send_mask(struct irq_data *d, const struct cpumask *mask)
|
||||
{
|
||||
struct aic_irq_chip *ic = irq_data_get_irq_chip_data(d);
|
||||
u32 irq_bit = BIT(irqd_to_hwirq(d));
|
||||
u32 send = 0;
|
||||
int cpu;
|
||||
unsigned long pending;
|
||||
|
||||
for_each_cpu(cpu, mask) {
|
||||
/*
|
||||
* This sequence is the mirror of the one in aic_ipi_unmask();
|
||||
* see the comment there. Additionally, release semantics
|
||||
* ensure that the vIPI flag set is ordered after any shared
|
||||
* memory accesses that precede it. This therefore also pairs
|
||||
* with the atomic_fetch_andnot in aic_handle_ipi().
|
||||
*/
|
||||
pending = atomic_fetch_or_release(irq_bit, per_cpu_ptr(&aic_vipi_flag, cpu));
|
||||
|
||||
/*
|
||||
* The atomic_fetch_or_release() above must complete before the
|
||||
* atomic_read() below to avoid racing aic_ipi_unmask().
|
||||
*/
|
||||
smp_mb__after_atomic();
|
||||
|
||||
if (!(pending & irq_bit) &&
|
||||
(atomic_read(per_cpu_ptr(&aic_vipi_enable, cpu)) & irq_bit)) {
|
||||
if (static_branch_likely(&use_fast_ipi))
|
||||
aic_ipi_send_fast(cpu);
|
||||
else
|
||||
send |= AIC_IPI_SEND_CPU(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The flag writes must complete before the physical IPI is issued
|
||||
* to another CPU. This is implied by the control dependency on
|
||||
* the result of atomic_read_acquire() above, which is itself
|
||||
* already ordered after the vIPI flag write.
|
||||
*/
|
||||
if (send)
|
||||
aic_ic_write(ic, AIC_IPI_SEND, send);
|
||||
}
|
||||
|
||||
static struct irq_chip ipi_chip = {
|
||||
.name = "AIC-IPI",
|
||||
.irq_mask = aic_ipi_mask,
|
||||
.irq_unmask = aic_ipi_unmask,
|
||||
.ipi_send_mask = aic_ipi_send_mask,
|
||||
};
|
||||
|
||||
/*
|
||||
* IPI IRQ domain
|
||||
*/
|
||||
|
||||
static void aic_handle_ipi(struct pt_regs *regs)
|
||||
{
|
||||
int i;
|
||||
unsigned long enabled, firing;
|
||||
|
||||
/*
|
||||
* Ack the IPI. We need to order this after the AIC event read, but
|
||||
* that is enforced by normal MMIO ordering guarantees.
|
||||
@@ -857,27 +763,7 @@ static void aic_handle_ipi(struct pt_regs *regs)
|
||||
aic_ic_write(aic_irqc, AIC_IPI_ACK, AIC_IPI_OTHER);
|
||||
}
|
||||
|
||||
/*
|
||||
* The mask read does not need to be ordered. Only we can change
|
||||
* our own mask anyway, so no races are possible here, as long as
|
||||
* we are properly in the interrupt handler (which is covered by
|
||||
* the barrier that is part of the top-level AIC handler's readl()).
|
||||
*/
|
||||
enabled = atomic_read(this_cpu_ptr(&aic_vipi_enable));
|
||||
|
||||
/*
|
||||
* Clear the IPIs we are about to handle. This pairs with the
|
||||
* atomic_fetch_or_release() in aic_ipi_send_mask(), and needs to be
|
||||
* ordered after the aic_ic_write() above (to avoid dropping vIPIs) and
|
||||
* before IPI handling code (to avoid races handling vIPIs before they
|
||||
* are signaled). The former is taken care of by the release semantics
|
||||
* of the write portion, while the latter is taken care of by the
|
||||
* acquire semantics of the read portion.
|
||||
*/
|
||||
firing = atomic_fetch_andnot(enabled, this_cpu_ptr(&aic_vipi_flag)) & enabled;
|
||||
|
||||
for_each_set_bit(i, &firing, AIC_NR_SWIPI)
|
||||
generic_handle_domain_irq(aic_irqc->ipi_domain, i);
|
||||
ipi_mux_process();
|
||||
|
||||
/*
|
||||
* No ordering needed here; at worst this just changes the timing of
|
||||
@@ -887,55 +773,24 @@ static void aic_handle_ipi(struct pt_regs *regs)
|
||||
aic_ic_write(aic_irqc, AIC_IPI_MASK_CLR, AIC_IPI_OTHER);
|
||||
}
|
||||
|
||||
static int aic_ipi_alloc(struct irq_domain *d, unsigned int virq,
|
||||
unsigned int nr_irqs, void *args)
|
||||
static void aic_ipi_send_single(unsigned int cpu)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_irqs; i++) {
|
||||
irq_set_percpu_devid(virq + i);
|
||||
irq_domain_set_info(d, virq + i, i, &ipi_chip, d->host_data,
|
||||
handle_percpu_devid_irq, NULL, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (static_branch_likely(&use_fast_ipi))
|
||||
aic_ipi_send_fast(cpu);
|
||||
else
|
||||
aic_ic_write(aic_irqc, AIC_IPI_SEND, AIC_IPI_SEND_CPU(cpu));
|
||||
}
|
||||
|
||||
static void aic_ipi_free(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs)
|
||||
{
|
||||
/* Not freeing IPIs */
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops aic_ipi_domain_ops = {
|
||||
.alloc = aic_ipi_alloc,
|
||||
.free = aic_ipi_free,
|
||||
};
|
||||
|
||||
static int __init aic_init_smp(struct aic_irq_chip *irqc, struct device_node *node)
|
||||
{
|
||||
struct irq_domain *ipi_domain;
|
||||
int base_ipi;
|
||||
|
||||
ipi_domain = irq_domain_create_linear(irqc->hw_domain->fwnode, AIC_NR_SWIPI,
|
||||
&aic_ipi_domain_ops, irqc);
|
||||
if (WARN_ON(!ipi_domain))
|
||||
base_ipi = ipi_mux_create(AIC_NR_SWIPI, aic_ipi_send_single);
|
||||
if (WARN_ON(base_ipi <= 0))
|
||||
return -ENODEV;
|
||||
|
||||
ipi_domain->flags |= IRQ_DOMAIN_FLAG_IPI_SINGLE;
|
||||
irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
|
||||
|
||||
base_ipi = __irq_domain_alloc_irqs(ipi_domain, -1, AIC_NR_SWIPI,
|
||||
NUMA_NO_NODE, NULL, false, NULL);
|
||||
|
||||
if (WARN_ON(!base_ipi)) {
|
||||
irq_domain_remove(ipi_domain);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
set_smp_ipi_range(base_ipi, AIC_NR_SWIPI);
|
||||
|
||||
irqc->ipi_domain = ipi_domain;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -454,8 +454,7 @@ static __init void armada_xp_ipi_init(struct device_node *node)
|
||||
return;
|
||||
|
||||
irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
|
||||
base_ipi = __irq_domain_alloc_irqs(ipi_domain, -1, IPI_DOORBELL_END,
|
||||
NUMA_NO_NODE, NULL, false, NULL);
|
||||
base_ipi = irq_domain_alloc_irqs(ipi_domain, IPI_DOORBELL_END, NUMA_NO_NODE, NULL);
|
||||
if (WARN_ON(!base_ipi))
|
||||
return;
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
|
||||
#define ASPEED_SCU_IC_REG 0x018
|
||||
#define ASPEED_SCU_IC_SHIFT 0
|
||||
#define ASPEED_SCU_IC_ENABLE GENMASK(6, ASPEED_SCU_IC_SHIFT)
|
||||
#define ASPEED_SCU_IC_ENABLE GENMASK(15, ASPEED_SCU_IC_SHIFT)
|
||||
#define ASPEED_SCU_IC_NUM_IRQS 7
|
||||
#define ASPEED_SCU_IC_STATUS GENMASK(28, 16)
|
||||
#define ASPEED_SCU_IC_STATUS_SHIFT 16
|
||||
|
||||
#define ASPEED_AST2600_SCU_IC0_REG 0x560
|
||||
@@ -155,6 +156,8 @@ static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic,
|
||||
rc = PTR_ERR(scu_ic->scu);
|
||||
goto err;
|
||||
}
|
||||
regmap_write_bits(scu_ic->scu, scu_ic->reg, ASPEED_SCU_IC_STATUS, ASPEED_SCU_IC_STATUS);
|
||||
regmap_write_bits(scu_ic->scu, scu_ic->reg, ASPEED_SCU_IC_ENABLE, 0);
|
||||
|
||||
irq = irq_of_parse_and_map(node, 0);
|
||||
if (!irq) {
|
||||
|
||||
@@ -268,10 +268,7 @@ static void __init bcm2836_arm_irqchip_smp_init(void)
|
||||
ipi_domain->flags |= IRQ_DOMAIN_FLAG_IPI_SINGLE;
|
||||
irq_domain_update_bus_token(ipi_domain, DOMAIN_BUS_IPI);
|
||||
|
||||
base_ipi = __irq_domain_alloc_irqs(ipi_domain, -1, BITS_PER_MBOX,
|
||||
NUMA_NO_NODE, NULL,
|
||||
false, NULL);
|
||||
|
||||
base_ipi = irq_domain_alloc_irqs(ipi_domain, BITS_PER_MBOX, NUMA_NO_NODE, NULL);
|
||||
if (WARN_ON(!base_ipi))
|
||||
return;
|
||||
|
||||
|
||||
@@ -279,7 +279,8 @@ static int __init bcm7120_l2_intc_probe(struct device_node *dn,
|
||||
flags |= IRQ_GC_BE_IO;
|
||||
|
||||
ret = irq_alloc_domain_generic_chips(data->domain, IRQS_PER_WORD, 1,
|
||||
dn->full_name, handle_level_irq, clr, 0, flags);
|
||||
dn->full_name, handle_level_irq, clr,
|
||||
IRQ_LEVEL, flags);
|
||||
if (ret) {
|
||||
pr_err("failed to allocate generic irq chip\n");
|
||||
goto out_free_domain;
|
||||
|
||||
@@ -161,6 +161,7 @@ static int __init brcmstb_l2_intc_of_init(struct device_node *np,
|
||||
*init_params)
|
||||
{
|
||||
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
|
||||
unsigned int set = 0;
|
||||
struct brcmstb_l2_intc_data *data;
|
||||
struct irq_chip_type *ct;
|
||||
int ret;
|
||||
@@ -208,9 +209,12 @@ static int __init brcmstb_l2_intc_of_init(struct device_node *np,
|
||||
if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
|
||||
flags |= IRQ_GC_BE_IO;
|
||||
|
||||
if (init_params->handler == handle_level_irq)
|
||||
set |= IRQ_LEVEL;
|
||||
|
||||
/* Allocate a single Generic IRQ chip for this node */
|
||||
ret = irq_alloc_domain_generic_chips(data->domain, 32, 1,
|
||||
np->full_name, init_params->handler, clr, 0, flags);
|
||||
np->full_name, init_params->handler, clr, set, flags);
|
||||
if (ret) {
|
||||
pr_err("failed to allocate generic irq chip\n");
|
||||
goto out_free_domain;
|
||||
|
||||
@@ -287,15 +287,14 @@ static __init int gicv2m_allocate_domains(struct irq_domain *parent)
|
||||
if (!v2m)
|
||||
return 0;
|
||||
|
||||
inner_domain = irq_domain_create_tree(v2m->fwnode,
|
||||
&gicv2m_domain_ops, v2m);
|
||||
inner_domain = irq_domain_create_hierarchy(parent, 0, 0, v2m->fwnode,
|
||||
&gicv2m_domain_ops, v2m);
|
||||
if (!inner_domain) {
|
||||
pr_err("Failed to create GICv2m domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
|
||||
inner_domain->parent = parent;
|
||||
pci_domain = pci_msi_create_irq_domain(v2m->fwnode,
|
||||
&gicv2m_msi_domain_info,
|
||||
inner_domain);
|
||||
|
||||
@@ -4909,18 +4909,19 @@ static int its_init_domain(struct fwnode_handle *handle, struct its_node *its)
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
inner_domain = irq_domain_create_tree(handle, &its_domain_ops, its);
|
||||
info->ops = &its_msi_domain_ops;
|
||||
info->data = its;
|
||||
|
||||
inner_domain = irq_domain_create_hierarchy(its_parent,
|
||||
its->msi_domain_flags, 0,
|
||||
handle, &its_domain_ops,
|
||||
info);
|
||||
if (!inner_domain) {
|
||||
kfree(info);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
inner_domain->parent = its_parent;
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
|
||||
inner_domain->flags |= its->msi_domain_flags;
|
||||
info->ops = &its_msi_domain_ops;
|
||||
info->data = its;
|
||||
inner_domain->host_data = info;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -233,13 +233,12 @@ static int mbi_allocate_domains(struct irq_domain *parent)
|
||||
struct irq_domain *nexus_domain, *pci_domain, *plat_domain;
|
||||
int err;
|
||||
|
||||
nexus_domain = irq_domain_create_tree(parent->fwnode,
|
||||
&mbi_domain_ops, NULL);
|
||||
nexus_domain = irq_domain_create_hierarchy(parent, 0, 0, parent->fwnode,
|
||||
&mbi_domain_ops, NULL);
|
||||
if (!nexus_domain)
|
||||
return -ENOMEM;
|
||||
|
||||
irq_domain_update_bus_token(nexus_domain, DOMAIN_BUS_NEXUS);
|
||||
nexus_domain->parent = parent;
|
||||
|
||||
err = mbi_allocate_pci_domain(nexus_domain, &pci_domain);
|
||||
|
||||
|
||||
@@ -1315,9 +1315,7 @@ static void __init gic_smp_init(void)
|
||||
gic_starting_cpu, NULL);
|
||||
|
||||
/* Register all 8 non-secure SGIs */
|
||||
base_sgi = __irq_domain_alloc_irqs(gic_data.domain, -1, 8,
|
||||
NUMA_NO_NODE, &sgi_fwspec,
|
||||
false, NULL);
|
||||
base_sgi = irq_domain_alloc_irqs(gic_data.domain, 8, NUMA_NO_NODE, &sgi_fwspec);
|
||||
if (WARN_ON(base_sgi <= 0))
|
||||
return;
|
||||
|
||||
|
||||
@@ -139,9 +139,7 @@ static int its_alloc_vcpu_sgis(struct its_vpe *vpe, int idx)
|
||||
if (!vpe->sgi_domain)
|
||||
goto err;
|
||||
|
||||
sgi_base = __irq_domain_alloc_irqs(vpe->sgi_domain, -1, 16,
|
||||
NUMA_NO_NODE, vpe,
|
||||
false, NULL);
|
||||
sgi_base = irq_domain_alloc_irqs(vpe->sgi_domain, 16, NUMA_NO_NODE, vpe);
|
||||
if (sgi_base <= 0)
|
||||
goto err;
|
||||
|
||||
@@ -176,9 +174,8 @@ int its_alloc_vcpu_irqs(struct its_vm *vm)
|
||||
vm->vpes[i]->idai = true;
|
||||
}
|
||||
|
||||
vpe_base_irq = __irq_domain_alloc_irqs(vm->domain, -1, vm->nr_vpes,
|
||||
NUMA_NO_NODE, vm,
|
||||
false, NULL);
|
||||
vpe_base_irq = irq_domain_alloc_irqs(vm->domain, vm->nr_vpes,
|
||||
NUMA_NO_NODE, vm);
|
||||
if (vpe_base_irq <= 0)
|
||||
goto err;
|
||||
|
||||
|
||||
@@ -871,9 +871,7 @@ static __init void gic_smp_init(void)
|
||||
"irqchip/arm/gic:starting",
|
||||
gic_starting_cpu, NULL);
|
||||
|
||||
base_sgi = __irq_domain_alloc_irqs(gic_data[0].domain, -1, 8,
|
||||
NUMA_NO_NODE, &sgi_fwspec,
|
||||
false, NULL);
|
||||
base_sgi = irq_domain_alloc_irqs(gic_data[0].domain, 8, NUMA_NO_NODE, &sgi_fwspec);
|
||||
if (WARN_ON(base_sgi <= 0))
|
||||
return;
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ struct liointc_priv {
|
||||
struct liointc_handler_data handler[LIOINTC_NUM_PARENT];
|
||||
void __iomem *core_isr[LIOINTC_NUM_CORES];
|
||||
u8 map_cache[LIOINTC_CHIP_IRQ];
|
||||
u32 int_pol;
|
||||
u32 int_edge;
|
||||
bool has_lpc_irq_errata;
|
||||
};
|
||||
|
||||
@@ -138,6 +140,14 @@ static int liointc_set_type(struct irq_data *data, unsigned int type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void liointc_suspend(struct irq_chip_generic *gc)
|
||||
{
|
||||
struct liointc_priv *priv = gc->private;
|
||||
|
||||
priv->int_pol = readl(gc->reg_base + LIOINTC_REG_INTC_POL);
|
||||
priv->int_edge = readl(gc->reg_base + LIOINTC_REG_INTC_EDGE);
|
||||
}
|
||||
|
||||
static void liointc_resume(struct irq_chip_generic *gc)
|
||||
{
|
||||
struct liointc_priv *priv = gc->private;
|
||||
@@ -150,6 +160,8 @@ static void liointc_resume(struct irq_chip_generic *gc)
|
||||
/* Restore map cache */
|
||||
for (i = 0; i < LIOINTC_CHIP_IRQ; i++)
|
||||
writeb(priv->map_cache[i], gc->reg_base + i);
|
||||
writel(priv->int_pol, gc->reg_base + LIOINTC_REG_INTC_POL);
|
||||
writel(priv->int_edge, gc->reg_base + LIOINTC_REG_INTC_EDGE);
|
||||
/* Restore mask cache */
|
||||
writel(gc->mask_cache, gc->reg_base + LIOINTC_REG_INTC_ENABLE);
|
||||
irq_gc_unlock_irqrestore(gc, flags);
|
||||
@@ -269,6 +281,7 @@ static int liointc_init(phys_addr_t addr, unsigned long size, int revision,
|
||||
gc->private = priv;
|
||||
gc->reg_base = base;
|
||||
gc->domain = domain;
|
||||
gc->suspend = liointc_suspend;
|
||||
gc->resume = liointc_resume;
|
||||
|
||||
ct = gc->chip_types;
|
||||
|
||||
@@ -163,16 +163,15 @@ static int pch_msi_init_domains(struct pch_msi_data *priv,
|
||||
{
|
||||
struct irq_domain *middle_domain, *msi_domain;
|
||||
|
||||
middle_domain = irq_domain_create_linear(domain_handle,
|
||||
priv->num_irqs,
|
||||
&pch_msi_middle_domain_ops,
|
||||
priv);
|
||||
middle_domain = irq_domain_create_hierarchy(parent, 0, priv->num_irqs,
|
||||
domain_handle,
|
||||
&pch_msi_middle_domain_ops,
|
||||
priv);
|
||||
if (!middle_domain) {
|
||||
pr_err("Failed to create the MSI middle domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
middle_domain->parent = parent;
|
||||
irq_domain_update_bus_token(middle_domain, DOMAIN_BUS_NEXUS);
|
||||
|
||||
msi_domain = pci_msi_create_irq_domain(domain_handle,
|
||||
|
||||
@@ -221,6 +221,7 @@ static int mvebu_gicp_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
parent_domain = irq_find_host(irq_parent_dn);
|
||||
of_node_put(irq_parent_dn);
|
||||
if (!parent_domain) {
|
||||
dev_err(&pdev->dev, "failed to find parent IRQ domain\n");
|
||||
return -ENODEV;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user