]>
Commit | Line | Data |
---|---|---|
60ec2440 TL |
1 | /* |
2 | * Resource Director Technology(RDT) | |
3 | * - Cache Allocation code. | |
4 | * | |
5 | * Copyright (C) 2016 Intel Corporation | |
6 | * | |
7 | * Authors: | |
8 | * Fenghua Yu <fenghua.yu@intel.com> | |
9 | * Tony Luck <tony.luck@intel.com> | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify it | |
12 | * under the terms and conditions of the GNU General Public License, | |
13 | * version 2, as published by the Free Software Foundation. | |
14 | * | |
15 | * This program is distributed in the hope it will be useful, but WITHOUT | |
16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
17 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
18 | * more details. | |
19 | * | |
20 | * More information about RDT be found in the Intel (R) x86 Architecture | |
21 | * Software Developer Manual June 2016, volume 3, section 17.17. | |
22 | */ | |
23 | ||
24 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
25 | ||
26 | #include <linux/kernfs.h> | |
27 | #include <linux/seq_file.h> | |
28 | #include <linux/slab.h> | |
29 | #include <asm/intel_rdt.h> | |
30 | ||
64e8ed3d VS |
31 | /* |
32 | * Check whether MBA bandwidth percentage value is correct. The value is | |
33 | * checked against the minimum and max bandwidth values specified by the | |
34 | * hardware. The allocated bandwidth percentage is rounded to the next | |
35 | * control step available on the hardware. | |
36 | */ | |
37 | static bool bw_validate(char *buf, unsigned long *data, struct rdt_resource *r) | |
38 | { | |
39 | unsigned long bw; | |
40 | int ret; | |
41 | ||
42 | /* | |
43 | * Only linear delay values is supported for current Intel SKUs. | |
44 | */ | |
45 | if (!r->membw.delay_linear) | |
46 | return false; | |
47 | ||
48 | ret = kstrtoul(buf, 10, &bw); | |
49 | if (ret) | |
50 | return false; | |
51 | ||
52 | if (bw < r->membw.min_bw || bw > r->default_ctrl) | |
53 | return false; | |
54 | ||
55 | *data = roundup(bw, (unsigned long)r->membw.bw_gran); | |
56 | return true; | |
57 | } | |
58 | ||
59 | int parse_bw(char *buf, struct rdt_resource *r, struct rdt_domain *d) | |
60 | { | |
61 | unsigned long data; | |
62 | ||
63 | if (d->have_new_ctrl) | |
64 | return -EINVAL; | |
65 | ||
66 | if (!bw_validate(buf, &data, r)) | |
67 | return -EINVAL; | |
68 | d->new_ctrl = data; | |
69 | d->have_new_ctrl = true; | |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
60ec2440 TL |
74 | /* |
75 | * Check whether a cache bit mask is valid. The SDM says: | |
76 | * Please note that all (and only) contiguous '1' combinations | |
77 | * are allowed (e.g. FFFFH, 0FF0H, 003CH, etc.). | |
78 | * Additionally Haswell requires at least two bits set. | |
79 | */ | |
c6ea67de | 80 | static bool cbm_validate(char *buf, unsigned long *data, struct rdt_resource *r) |
60ec2440 | 81 | { |
c6ea67de | 82 | unsigned long first_bit, zero_bit, val; |
d3e11b4d | 83 | unsigned int cbm_len = r->cache.cbm_len; |
c6ea67de VS |
84 | int ret; |
85 | ||
86 | ret = kstrtoul(buf, 16, &val); | |
87 | if (ret) | |
88 | return false; | |
60ec2440 | 89 | |
c6ea67de | 90 | if (val == 0 || val > r->default_ctrl) |
60ec2440 TL |
91 | return false; |
92 | ||
c6ea67de VS |
93 | first_bit = find_first_bit(&val, cbm_len); |
94 | zero_bit = find_next_zero_bit(&val, cbm_len, first_bit); | |
60ec2440 | 95 | |
c6ea67de | 96 | if (find_next_bit(&val, cbm_len, zero_bit) < cbm_len) |
60ec2440 TL |
97 | return false; |
98 | ||
d3e11b4d | 99 | if ((zero_bit - first_bit) < r->cache.min_cbm_bits) |
60ec2440 | 100 | return false; |
c6ea67de VS |
101 | |
102 | *data = val; | |
60ec2440 TL |
103 | return true; |
104 | } | |
105 | ||
106 | /* | |
107 | * Read one cache bit mask (hex). Check that it is valid for the current | |
108 | * resource type. | |
109 | */ | |
c6ea67de | 110 | int parse_cbm(char *buf, struct rdt_resource *r, struct rdt_domain *d) |
60ec2440 TL |
111 | { |
112 | unsigned long data; | |
60ec2440 | 113 | |
2545e9f5 | 114 | if (d->have_new_ctrl) |
c4026b7b TL |
115 | return -EINVAL; |
116 | ||
c6ea67de | 117 | if(!cbm_validate(buf, &data, r)) |
60ec2440 | 118 | return -EINVAL; |
2545e9f5 VS |
119 | d->new_ctrl = data; |
120 | d->have_new_ctrl = true; | |
60ec2440 TL |
121 | |
122 | return 0; | |
123 | } | |
124 | ||
125 | /* | |
126 | * For each domain in this resource we expect to find a series of: | |
127 | * id=mask | |
c4026b7b TL |
128 | * separated by ";". The "id" is in decimal, and must match one of |
129 | * the "id"s for this resource. | |
60ec2440 TL |
130 | */ |
131 | static int parse_line(char *line, struct rdt_resource *r) | |
132 | { | |
133 | char *dom = NULL, *id; | |
134 | struct rdt_domain *d; | |
135 | unsigned long dom_id; | |
136 | ||
c4026b7b TL |
137 | next: |
138 | if (!line || line[0] == '\0') | |
139 | return 0; | |
140 | dom = strsep(&line, ";"); | |
141 | id = strsep(&dom, "="); | |
142 | if (!dom || kstrtoul(id, 10, &dom_id)) | |
143 | return -EINVAL; | |
634b0e04 | 144 | dom = strim(dom); |
60ec2440 | 145 | list_for_each_entry(d, &r->domains, list) { |
c4026b7b | 146 | if (d->id == dom_id) { |
c6ea67de | 147 | if (r->parse_ctrlval(dom, r, d)) |
c4026b7b TL |
148 | return -EINVAL; |
149 | goto next; | |
150 | } | |
60ec2440 | 151 | } |
c4026b7b | 152 | return -EINVAL; |
60ec2440 TL |
153 | } |
154 | ||
155 | static int update_domains(struct rdt_resource *r, int closid) | |
156 | { | |
157 | struct msr_param msr_param; | |
158 | cpumask_var_t cpu_mask; | |
159 | struct rdt_domain *d; | |
c4026b7b | 160 | int cpu; |
60ec2440 TL |
161 | |
162 | if (!zalloc_cpumask_var(&cpu_mask, GFP_KERNEL)) | |
163 | return -ENOMEM; | |
164 | ||
165 | msr_param.low = closid; | |
166 | msr_param.high = msr_param.low + 1; | |
167 | msr_param.res = r; | |
168 | ||
169 | list_for_each_entry(d, &r->domains, list) { | |
2545e9f5 | 170 | if (d->have_new_ctrl && d->new_ctrl != d->ctrl_val[closid]) { |
c4026b7b | 171 | cpumask_set_cpu(cpumask_any(&d->cpu_mask), cpu_mask); |
2545e9f5 | 172 | d->ctrl_val[closid] = d->new_ctrl; |
c4026b7b | 173 | } |
60ec2440 | 174 | } |
c4026b7b TL |
175 | if (cpumask_empty(cpu_mask)) |
176 | goto done; | |
60ec2440 TL |
177 | cpu = get_cpu(); |
178 | /* Update CBM on this cpu if it's in cpu_mask. */ | |
179 | if (cpumask_test_cpu(cpu, cpu_mask)) | |
2545e9f5 | 180 | rdt_ctrl_update(&msr_param); |
60ec2440 | 181 | /* Update CBM on other cpus. */ |
2545e9f5 | 182 | smp_call_function_many(cpu_mask, rdt_ctrl_update, &msr_param, 1); |
60ec2440 TL |
183 | put_cpu(); |
184 | ||
c4026b7b | 185 | done: |
60ec2440 TL |
186 | free_cpumask_var(cpu_mask); |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | ssize_t rdtgroup_schemata_write(struct kernfs_open_file *of, | |
192 | char *buf, size_t nbytes, loff_t off) | |
193 | { | |
194 | struct rdtgroup *rdtgrp; | |
c4026b7b | 195 | struct rdt_domain *dom; |
60ec2440 TL |
196 | struct rdt_resource *r; |
197 | char *tok, *resname; | |
198 | int closid, ret = 0; | |
60ec2440 TL |
199 | |
200 | /* Valid input requires a trailing newline */ | |
201 | if (nbytes == 0 || buf[nbytes - 1] != '\n') | |
202 | return -EINVAL; | |
203 | buf[nbytes - 1] = '\0'; | |
204 | ||
205 | rdtgrp = rdtgroup_kn_lock_live(of->kn); | |
206 | if (!rdtgrp) { | |
207 | rdtgroup_kn_unlock(of->kn); | |
208 | return -ENOENT; | |
209 | } | |
210 | ||
211 | closid = rdtgrp->closid; | |
212 | ||
c4026b7b TL |
213 | for_each_enabled_rdt_resource(r) |
214 | list_for_each_entry(dom, &r->domains, list) | |
2545e9f5 | 215 | dom->have_new_ctrl = false; |
60ec2440 TL |
216 | |
217 | while ((tok = strsep(&buf, "\n")) != NULL) { | |
634b0e04 | 218 | resname = strim(strsep(&tok, ":")); |
60ec2440 TL |
219 | if (!tok) { |
220 | ret = -EINVAL; | |
221 | goto out; | |
222 | } | |
223 | for_each_enabled_rdt_resource(r) { | |
224 | if (!strcmp(resname, r->name) && | |
225 | closid < r->num_closid) { | |
226 | ret = parse_line(tok, r); | |
227 | if (ret) | |
228 | goto out; | |
229 | break; | |
230 | } | |
231 | } | |
232 | if (!r->name) { | |
233 | ret = -EINVAL; | |
234 | goto out; | |
235 | } | |
236 | } | |
237 | ||
60ec2440 TL |
238 | for_each_enabled_rdt_resource(r) { |
239 | ret = update_domains(r, closid); | |
240 | if (ret) | |
241 | goto out; | |
242 | } | |
243 | ||
244 | out: | |
245 | rdtgroup_kn_unlock(of->kn); | |
60ec2440 TL |
246 | return ret ?: nbytes; |
247 | } | |
248 | ||
249 | static void show_doms(struct seq_file *s, struct rdt_resource *r, int closid) | |
250 | { | |
251 | struct rdt_domain *dom; | |
252 | bool sep = false; | |
253 | ||
de016df8 | 254 | seq_printf(s, "%*s:", max_name_width, r->name); |
60ec2440 TL |
255 | list_for_each_entry(dom, &r->domains, list) { |
256 | if (sep) | |
257 | seq_puts(s, ";"); | |
c6ea67de | 258 | seq_printf(s, r->format_str, dom->id, max_data_width, |
2545e9f5 | 259 | dom->ctrl_val[closid]); |
60ec2440 TL |
260 | sep = true; |
261 | } | |
262 | seq_puts(s, "\n"); | |
263 | } | |
264 | ||
265 | int rdtgroup_schemata_show(struct kernfs_open_file *of, | |
266 | struct seq_file *s, void *v) | |
267 | { | |
268 | struct rdtgroup *rdtgrp; | |
269 | struct rdt_resource *r; | |
270 | int closid, ret = 0; | |
271 | ||
272 | rdtgrp = rdtgroup_kn_lock_live(of->kn); | |
273 | if (rdtgrp) { | |
274 | closid = rdtgrp->closid; | |
275 | for_each_enabled_rdt_resource(r) { | |
276 | if (closid < r->num_closid) | |
277 | show_doms(s, r, closid); | |
278 | } | |
279 | } else { | |
280 | ret = -ENOENT; | |
281 | } | |
282 | rdtgroup_kn_unlock(of->kn); | |
283 | return ret; | |
284 | } |