eal: use callbacks for power monitoring comparison
[dpdk.git] / lib / eal / x86 / rte_power_intrinsics.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2020 Intel Corporation
3  */
4
5 #include <rte_common.h>
6 #include <rte_lcore.h>
7 #include <rte_spinlock.h>
8
9 #include "rte_power_intrinsics.h"
10
11 /*
12  * Per-lcore structure holding current status of C0.2 sleeps.
13  */
14 static struct power_wait_status {
15         rte_spinlock_t lock;
16         volatile void *monitor_addr; /**< NULL if not currently sleeping */
17 } __rte_cache_aligned wait_status[RTE_MAX_LCORE];
18
19 static inline void
20 __umwait_wakeup(volatile void *addr)
21 {
22         uint64_t val;
23
24         /* trigger a write but don't change the value */
25         val = __atomic_load_n((volatile uint64_t *)addr, __ATOMIC_RELAXED);
26         __atomic_compare_exchange_n((volatile uint64_t *)addr, &val, val, 0,
27                         __ATOMIC_RELAXED, __ATOMIC_RELAXED);
28 }
29
30 static bool wait_supported;
31
32 static inline uint64_t
33 __get_umwait_val(const volatile void *p, const uint8_t sz)
34 {
35         switch (sz) {
36         case sizeof(uint8_t):
37                 return *(const volatile uint8_t *)p;
38         case sizeof(uint16_t):
39                 return *(const volatile uint16_t *)p;
40         case sizeof(uint32_t):
41                 return *(const volatile uint32_t *)p;
42         case sizeof(uint64_t):
43                 return *(const volatile uint64_t *)p;
44         default:
45                 /* shouldn't happen */
46                 RTE_ASSERT(0);
47                 return 0;
48         }
49 }
50
51 static inline int
52 __check_val_size(const uint8_t sz)
53 {
54         switch (sz) {
55         case sizeof(uint8_t):  /* fall-through */
56         case sizeof(uint16_t): /* fall-through */
57         case sizeof(uint32_t): /* fall-through */
58         case sizeof(uint64_t): /* fall-through */
59                 return 0;
60         default:
61                 /* unexpected size */
62                 return -1;
63         }
64 }
65
66 /**
67  * This function uses UMONITOR/UMWAIT instructions and will enter C0.2 state.
68  * For more information about usage of these instructions, please refer to
69  * Intel(R) 64 and IA-32 Architectures Software Developer's Manual.
70  */
71 int
72 rte_power_monitor(const struct rte_power_monitor_cond *pmc,
73                 const uint64_t tsc_timestamp)
74 {
75         const uint32_t tsc_l = (uint32_t)tsc_timestamp;
76         const uint32_t tsc_h = (uint32_t)(tsc_timestamp >> 32);
77         const unsigned int lcore_id = rte_lcore_id();
78         struct power_wait_status *s;
79         uint64_t cur_value;
80
81         /* prevent user from running this instruction if it's not supported */
82         if (!wait_supported)
83                 return -ENOTSUP;
84
85         /* prevent non-EAL thread from using this API */
86         if (lcore_id >= RTE_MAX_LCORE)
87                 return -EINVAL;
88
89         if (pmc == NULL)
90                 return -EINVAL;
91
92         if (__check_val_size(pmc->size) < 0)
93                 return -EINVAL;
94
95         if (pmc->fn == NULL)
96                 return -EINVAL;
97
98         s = &wait_status[lcore_id];
99
100         /* update sleep address */
101         rte_spinlock_lock(&s->lock);
102         s->monitor_addr = pmc->addr;
103
104         /*
105          * we're using raw byte codes for now as only the newest compiler
106          * versions support this instruction natively.
107          */
108
109         /* set address for UMONITOR */
110         asm volatile(".byte 0xf3, 0x0f, 0xae, 0xf7;"
111                         :
112                         : "D"(pmc->addr));
113
114         /* now that we've put this address into monitor, we can unlock */
115         rte_spinlock_unlock(&s->lock);
116
117         cur_value = __get_umwait_val(pmc->addr, pmc->size);
118
119         /* check if callback indicates we should abort */
120         if (pmc->fn(cur_value, pmc->opaque) != 0)
121                 goto end;
122
123         /* execute UMWAIT */
124         asm volatile(".byte 0xf2, 0x0f, 0xae, 0xf7;"
125                         : /* ignore rflags */
126                         : "D"(0), /* enter C0.2 */
127                           "a"(tsc_l), "d"(tsc_h));
128
129 end:
130         /* erase sleep address */
131         rte_spinlock_lock(&s->lock);
132         s->monitor_addr = NULL;
133         rte_spinlock_unlock(&s->lock);
134
135         return 0;
136 }
137
138 /**
139  * This function uses TPAUSE instruction  and will enter C0.2 state. For more
140  * information about usage of this instruction, please refer to Intel(R) 64 and
141  * IA-32 Architectures Software Developer's Manual.
142  */
143 int
144 rte_power_pause(const uint64_t tsc_timestamp)
145 {
146         const uint32_t tsc_l = (uint32_t)tsc_timestamp;
147         const uint32_t tsc_h = (uint32_t)(tsc_timestamp >> 32);
148
149         /* prevent user from running this instruction if it's not supported */
150         if (!wait_supported)
151                 return -ENOTSUP;
152
153         /* execute TPAUSE */
154         asm volatile(".byte 0x66, 0x0f, 0xae, 0xf7;"
155                         : /* ignore rflags */
156                         : "D"(0), /* enter C0.2 */
157                         "a"(tsc_l), "d"(tsc_h));
158
159         return 0;
160 }
161
162 RTE_INIT(rte_power_intrinsics_init) {
163         struct rte_cpu_intrinsics i;
164
165         rte_cpu_get_intrinsics_support(&i);
166
167         if (i.power_monitor && i.power_pause)
168                 wait_supported = 1;
169 }
170
171 int
172 rte_power_monitor_wakeup(const unsigned int lcore_id)
173 {
174         struct power_wait_status *s;
175
176         /* prevent user from running this instruction if it's not supported */
177         if (!wait_supported)
178                 return -ENOTSUP;
179
180         /* prevent buffer overrun */
181         if (lcore_id >= RTE_MAX_LCORE)
182                 return -EINVAL;
183
184         s = &wait_status[lcore_id];
185
186         /*
187          * There is a race condition between sleep, wakeup and locking, but we
188          * don't need to handle it.
189          *
190          * Possible situations:
191          *
192          * 1. T1 locks, sets address, unlocks
193          * 2. T2 locks, triggers wakeup, unlocks
194          * 3. T1 sleeps
195          *
196          * In this case, because T1 has already set the address for monitoring,
197          * we will wake up immediately even if T2 triggers wakeup before T1
198          * goes to sleep.
199          *
200          * 1. T1 locks, sets address, unlocks, goes to sleep, and wakes up
201          * 2. T2 locks, triggers wakeup, and unlocks
202          * 3. T1 locks, erases address, and unlocks
203          *
204          * In this case, since we've already woken up, the "wakeup" was
205          * unneeded, and since T1 is still waiting on T2 releasing the lock, the
206          * wakeup address is still valid so it's perfectly safe to write it.
207          */
208         rte_spinlock_lock(&s->lock);
209         if (s->monitor_addr != NULL)
210                 __umwait_wakeup(s->monitor_addr);
211         rte_spinlock_unlock(&s->lock);
212
213         return 0;
214 }