summaryrefslogtreecommitdiff
path: root/src/dtpm/balance.c
blob: 0917d0b288e4319eb69a7c76adcd5967df8f5423 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#include "balance.h"

typedef unsigned long long u64;

#define DIV_ROUND_CLOSEST(x, divisor)(                  \
{                                                       \
        typeof(x) __x = x;                              \
        typeof(divisor) __d = divisor;                  \
        (((typeof(x))-1) > 0 ||                         \
         ((typeof(divisor))-1) > 0 ||                   \
         (((__x) > 0) == ((__d) > 0))) ?                \
                (((__x) + ((__d) / 2)) / (__d)) :       \
                (((__x) - ((__d) / 2)) / (__d));        \
}                                                       \
)

struct dtpm_balance {
	u64 min_power;
	u64 max_power;
	u64 cur_power;
	u64 power_limit;
	u64 static_power_limit;
	u64 dynamic_power_limit;
};

u64 powercap_em_get_power(struct powercap_em *pcem)
{
	u64 power = 0;
	int i;

	/*
	 * Only the leaves of the tree can return the real power,
	 * otherwise we return the sum of the children power.
	 */
	if (!pcem->nr_children)
		return pcem->cur_power;

	for (i = 0; i < pcem->nr_children; i++)
		power += powercap_em_get_power(pcem->children[i]);

	return power;
}

int powercap_em_rebalance_weight(struct powercap_em *pcem)
{
	struct powercap_em *child;
	int i;

	for (i = 0; i < pcem->nr_children; i++) {
		child = pcem->children[i];
		child->weight = DIV_ROUND_CLOSEST(child->max_power * 1024,
						  pcem->max_power);
	}

	return 0;
}

int powercap_em_set_static_power_limit(struct powercap_em *pcem, u64 power);

int powercap_em_rebalance_limit(struct powercap_em *pcem)
{
	if (!pcem)
		BUG();

	if (pcem->power_limit)
		return powercap_em_set_static_power_limit(pcem, pcem->power_limit);

	return powercap_em_rebalance_limit(pcem->parent);
}

int powercap_em_rebalance_power(struct powercap_em *pcem)
{
	struct powercap_em *donees[MAX_CHILDREN];
	struct powercap_em *donors[MAX_CHILDREN];
	int nr_donee, nr_donor, sum_weight;
	u64 power, power_free;
	int i, j;

again:
	nr_donee = nr_donor = sum_weight = 0;
	
	/*
	 * This first pass build the list of the donees and the sum of
	 * their weights.
	 */
	for (i = 0; i < pcem->nr_children; i++) {
		struct powercap_em *child;

		child = pcem->children[i];
		power = powercap_em_get_power(child);

		if (power > child->dynamic_power_limit) {

			/*
			 * We have one device which is consuming more
			 * energy than allocated but actually it
			 * donated the free power it had to another
			 * device. Now it is requesting more power, so
			 * it must get back the power it gave. Reset
			 * the dynamic power limit to the static power
			 * limit and do the rebalancing again.
			 */
			if (power < child->static_power_limit) {
				powercap_em_rebalance_limit(pcem);
				goto again;
			}

			donees[nr_donee++] = child;
			sum_weight += child->weight;
			continue;
		}

		if (power < child->dynamic_power_limit)
			donors[nr_donor++] = child;
	}

	/*
	 * There are no donees, so no need to rebalance the power
	 * across the actors, this is done, bail out.
	 */
	if (!nr_donee)
		return 0;

	/*
	 * We are in a situation where there is no more free power for
	 * the donees, so we need to reduce their power.
	 */
	if (!nr_donor) {

		/*
		 * We are in the situation where all the power was
		 * rebalanced along the actors but more power is
		 * consumed, in this case we need to cap their power.
		 */
		for (i = 0; i < nr_donee; i++)
			donees[i]->cur_power =
				donees[i]->dynamic_power_limit;

		return 0;
	}
	
	/*
	 * One or more actors are consuming more than the allocated
	 * power. Let's distribute the free power not used by the
	 * other actors which are below their limit.
	 */
	for (i = 0; i < nr_donor; i++) {

		struct powercap_em *donor = donors[i];

                power = powercap_em_get_power(donor);

		/*
		 * Compute the unused power ...
		 */
		power_free = donor->dynamic_power_limit - power;

		/*
		 * ... and as it will be given to the donees,
		 * remove this power from the allocated budget from
		 * the donor.
		 */
		donor->dynamic_power_limit = power;
		
		/*
		 * Redistribute the unused power to the donees.
		 */
		for (j = 0; j < nr_donee; j++) {
			donees[j]->dynamic_power_limit +=
				DIV_ROUND_CLOSEST(
					power_free * donees[j]->weight,
					sum_weight);
		}
	}

	/*
	 * Let's find out new donees and new donors
	 */
	goto again;

	/* Never reached */
	return -EINVAL;
}

int powercap_em_set_power(struct powercap_em *pcem, u64 power)
{
	/*
	 * This function can only be called by the leaves of the
	 * hierarchy tree which represent the devices on the
	 * system. Other nodes in the tree are aggregating the
	 * constraints and power information of the children.
	 */
	if (pcem->nr_children)
		return -EINVAL;

	if (power < pcem->min_power || power > pcem->max_power)
		return -EINVAL;
	
	pcem->cur_power = power;

	/*
	 * The power of this device changed, let's check if there is a
	 * limit to rebalance the power.
	 */
	while (pcem) {

		if (pcem->power_limit)
			return powercap_em_rebalance_power(pcem);

		pcem = pcem->parent;
	}

	return 0;
}

int powercap_em_set_static_power_limit(struct powercap_em *pcem, u64 power_limit)
{
	struct powercap_em *child;
	u64 static_power_limit;
	int i, ret;

	if (!pcem->nr_children) {
		pcem->dynamic_power_limit = power_limit;
		pcem->static_power_limit = power_limit;
		return 0;
	}

	for (i = 0; i < pcem->nr_children; i++) {
		child = pcem->children[i];

		static_power_limit = DIV_ROUND_CLOSEST(
			power_limit * child->weight, 1024);

		ret = powercap_em_set_static_power_limit(child,
							 static_power_limit);
		if (ret) 
			return ret;
	}
	
	return 0;
}

int powercap_em_set_power_limit(struct powercap_em *pcem, u64 power_limit)
{
	int ret;

	/*
	 * A power limit set to zero means we remove the constraint
	 */
	if (power_limit && (power_limit < pcem->min_power ||
			    power_limit > pcem->max_power))
		return -EINVAL;

	ret = powercap_em_set_static_power_limit(pcem, power_limit);
	if (ret)
		return ret;

	ret = powercap_em_rebalance_power(pcem);
	if (ret)
		goto out_undo;
	
	pcem->power_limit = power_limit;

	return 0;

out_undo:
	powercap_em_set_static_power_limit(pcem, 0);

	return ret;
}