Commit | Line | Data |
---|---|---|
c8e7b4d0 SP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * DAMON sysfs Interface | |
4 | * | |
5 | * Copyright (c) 2022 SeongJae Park <sj@kernel.org> | |
6 | */ | |
7 | ||
8 | #include <linux/slab.h> | |
9 | ||
10 | #include "sysfs-common.h" | |
11 | ||
9277d036 SP |
12 | /* |
13 | * scheme region directory | |
14 | */ | |
15 | ||
16 | struct damon_sysfs_scheme_region { | |
17 | struct kobject kobj; | |
18 | struct damon_addr_range ar; | |
19 | unsigned int nr_accesses; | |
20 | unsigned int age; | |
21 | struct list_head list; | |
22 | }; | |
23 | ||
24 | static struct damon_sysfs_scheme_region *damon_sysfs_scheme_region_alloc( | |
25 | struct damon_region *region) | |
26 | { | |
27 | struct damon_sysfs_scheme_region *sysfs_region = kmalloc( | |
28 | sizeof(*sysfs_region), GFP_KERNEL); | |
29 | ||
30 | if (!sysfs_region) | |
31 | return NULL; | |
32 | sysfs_region->kobj = (struct kobject){}; | |
33 | sysfs_region->ar = region->ar; | |
e7639bb4 | 34 | sysfs_region->nr_accesses = region->nr_accesses_bp / 10000; |
9277d036 SP |
35 | sysfs_region->age = region->age; |
36 | INIT_LIST_HEAD(&sysfs_region->list); | |
37 | return sysfs_region; | |
38 | } | |
39 | ||
40 | static ssize_t start_show(struct kobject *kobj, struct kobj_attribute *attr, | |
41 | char *buf) | |
42 | { | |
43 | struct damon_sysfs_scheme_region *region = container_of(kobj, | |
44 | struct damon_sysfs_scheme_region, kobj); | |
45 | ||
46 | return sysfs_emit(buf, "%lu\n", region->ar.start); | |
47 | } | |
48 | ||
49 | static ssize_t end_show(struct kobject *kobj, struct kobj_attribute *attr, | |
50 | char *buf) | |
51 | { | |
52 | struct damon_sysfs_scheme_region *region = container_of(kobj, | |
53 | struct damon_sysfs_scheme_region, kobj); | |
54 | ||
55 | return sysfs_emit(buf, "%lu\n", region->ar.end); | |
56 | } | |
57 | ||
58 | static ssize_t nr_accesses_show(struct kobject *kobj, | |
59 | struct kobj_attribute *attr, char *buf) | |
60 | { | |
61 | struct damon_sysfs_scheme_region *region = container_of(kobj, | |
62 | struct damon_sysfs_scheme_region, kobj); | |
63 | ||
64 | return sysfs_emit(buf, "%u\n", region->nr_accesses); | |
65 | } | |
66 | ||
67 | static ssize_t age_show(struct kobject *kobj, struct kobj_attribute *attr, | |
68 | char *buf) | |
69 | { | |
70 | struct damon_sysfs_scheme_region *region = container_of(kobj, | |
71 | struct damon_sysfs_scheme_region, kobj); | |
72 | ||
73 | return sysfs_emit(buf, "%u\n", region->age); | |
74 | } | |
75 | ||
76 | static void damon_sysfs_scheme_region_release(struct kobject *kobj) | |
77 | { | |
78 | struct damon_sysfs_scheme_region *region = container_of(kobj, | |
79 | struct damon_sysfs_scheme_region, kobj); | |
80 | ||
81 | list_del(®ion->list); | |
82 | kfree(region); | |
83 | } | |
84 | ||
85 | static struct kobj_attribute damon_sysfs_scheme_region_start_attr = | |
86 | __ATTR_RO_MODE(start, 0400); | |
87 | ||
88 | static struct kobj_attribute damon_sysfs_scheme_region_end_attr = | |
89 | __ATTR_RO_MODE(end, 0400); | |
90 | ||
91 | static struct kobj_attribute damon_sysfs_scheme_region_nr_accesses_attr = | |
92 | __ATTR_RO_MODE(nr_accesses, 0400); | |
93 | ||
94 | static struct kobj_attribute damon_sysfs_scheme_region_age_attr = | |
95 | __ATTR_RO_MODE(age, 0400); | |
96 | ||
97 | static struct attribute *damon_sysfs_scheme_region_attrs[] = { | |
98 | &damon_sysfs_scheme_region_start_attr.attr, | |
99 | &damon_sysfs_scheme_region_end_attr.attr, | |
100 | &damon_sysfs_scheme_region_nr_accesses_attr.attr, | |
101 | &damon_sysfs_scheme_region_age_attr.attr, | |
102 | NULL, | |
103 | }; | |
104 | ATTRIBUTE_GROUPS(damon_sysfs_scheme_region); | |
105 | ||
e56397e8 | 106 | static const struct kobj_type damon_sysfs_scheme_region_ktype = { |
9277d036 SP |
107 | .release = damon_sysfs_scheme_region_release, |
108 | .sysfs_ops = &kobj_sysfs_ops, | |
109 | .default_groups = damon_sysfs_scheme_region_groups, | |
110 | }; | |
111 | ||
5181b75f SP |
112 | /* |
113 | * scheme regions directory | |
114 | */ | |
115 | ||
4d4e41b6 SP |
116 | /* |
117 | * enum damos_sysfs_regions_upd_status - Represent DAMOS tried regions update | |
118 | * status | |
119 | * @DAMOS_TRIED_REGIONS_UPD_IDLE: Waiting for next request. | |
120 | * @DAMOS_TRIED_REGIONS_UPD_STARTED: Update started. | |
121 | * @DAMOS_TRIED_REGIONS_UPD_FINISHED: Update finished. | |
122 | * | |
123 | * Each DAMON-based operation scheme (&struct damos) has its own apply | |
124 | * interval, and we need to expose the scheme tried regions based on only | |
125 | * single snapshot. For this, we keep the tried regions update status for each | |
126 | * scheme. The status becomes 'idle' at the beginning. | |
127 | * | |
128 | * Once the tried regions update request is received, the request handling | |
129 | * start function (damon_sysfs_scheme_update_regions_start()) sets the status | |
130 | * of all schemes as 'idle' again, and register ->before_damos_apply() and | |
131 | * ->after_sampling() callbacks. | |
132 | * | |
133 | * Then, the first followup ->before_damos_apply() callback | |
134 | * (damon_sysfs_before_damos_apply()) sets the status 'started'. The first | |
135 | * ->after_sampling() callback (damon_sysfs_after_sampling()) after the call | |
136 | * is called only after the scheme is completely applied | |
137 | * to the given snapshot. Hence the callback knows the situation by showing | |
138 | * 'started' status, and sets the status as 'finished'. Then, | |
139 | * damon_sysfs_before_damos_apply() understands the situation by showing the | |
140 | * 'finished' status and do nothing. | |
141 | * | |
142 | * Finally, the tried regions request handling finisher function | |
143 | * (damon_sysfs_schemes_update_regions_stop()) unregisters the callbacks. | |
144 | */ | |
145 | enum damos_sysfs_regions_upd_status { | |
146 | DAMOS_TRIED_REGIONS_UPD_IDLE, | |
147 | DAMOS_TRIED_REGIONS_UPD_STARTED, | |
148 | DAMOS_TRIED_REGIONS_UPD_FINISHED, | |
149 | }; | |
150 | ||
5181b75f SP |
151 | struct damon_sysfs_scheme_regions { |
152 | struct kobject kobj; | |
9277d036 SP |
153 | struct list_head regions_list; |
154 | int nr_regions; | |
b69f92a7 | 155 | unsigned long total_bytes; |
4d4e41b6 | 156 | enum damos_sysfs_regions_upd_status upd_status; |
5181b75f SP |
157 | }; |
158 | ||
159 | static struct damon_sysfs_scheme_regions * | |
160 | damon_sysfs_scheme_regions_alloc(void) | |
161 | { | |
9277d036 SP |
162 | struct damon_sysfs_scheme_regions *regions = kmalloc(sizeof(*regions), |
163 | GFP_KERNEL); | |
164 | ||
165 | regions->kobj = (struct kobject){}; | |
166 | INIT_LIST_HEAD(®ions->regions_list); | |
167 | regions->nr_regions = 0; | |
b69f92a7 | 168 | regions->total_bytes = 0; |
4d4e41b6 | 169 | regions->upd_status = DAMOS_TRIED_REGIONS_UPD_IDLE; |
9277d036 SP |
170 | return regions; |
171 | } | |
172 | ||
b69f92a7 SP |
173 | static ssize_t total_bytes_show(struct kobject *kobj, |
174 | struct kobj_attribute *attr, char *buf) | |
175 | { | |
176 | struct damon_sysfs_scheme_regions *regions = container_of(kobj, | |
177 | struct damon_sysfs_scheme_regions, kobj); | |
178 | ||
179 | return sysfs_emit(buf, "%lu\n", regions->total_bytes); | |
180 | } | |
181 | ||
9277d036 SP |
182 | static void damon_sysfs_scheme_regions_rm_dirs( |
183 | struct damon_sysfs_scheme_regions *regions) | |
184 | { | |
185 | struct damon_sysfs_scheme_region *r, *next; | |
186 | ||
187 | list_for_each_entry_safe(r, next, ®ions->regions_list, list) { | |
188 | /* release function deletes it from the list */ | |
189 | kobject_put(&r->kobj); | |
190 | regions->nr_regions--; | |
191 | } | |
5181b75f SP |
192 | } |
193 | ||
194 | static void damon_sysfs_scheme_regions_release(struct kobject *kobj) | |
195 | { | |
196 | kfree(container_of(kobj, struct damon_sysfs_scheme_regions, kobj)); | |
197 | } | |
198 | ||
b69f92a7 SP |
199 | static struct kobj_attribute damon_sysfs_scheme_regions_total_bytes_attr = |
200 | __ATTR_RO_MODE(total_bytes, 0400); | |
201 | ||
5181b75f | 202 | static struct attribute *damon_sysfs_scheme_regions_attrs[] = { |
b69f92a7 | 203 | &damon_sysfs_scheme_regions_total_bytes_attr.attr, |
5181b75f SP |
204 | NULL, |
205 | }; | |
206 | ATTRIBUTE_GROUPS(damon_sysfs_scheme_regions); | |
207 | ||
e56397e8 | 208 | static const struct kobj_type damon_sysfs_scheme_regions_ktype = { |
5181b75f SP |
209 | .release = damon_sysfs_scheme_regions_release, |
210 | .sysfs_ops = &kobj_sysfs_ops, | |
211 | .default_groups = damon_sysfs_scheme_regions_groups, | |
212 | }; | |
213 | ||
c8e7b4d0 SP |
214 | /* |
215 | * schemes/stats directory | |
216 | */ | |
217 | ||
218 | struct damon_sysfs_stats { | |
219 | struct kobject kobj; | |
220 | unsigned long nr_tried; | |
221 | unsigned long sz_tried; | |
222 | unsigned long nr_applied; | |
223 | unsigned long sz_applied; | |
224 | unsigned long qt_exceeds; | |
225 | }; | |
226 | ||
227 | static struct damon_sysfs_stats *damon_sysfs_stats_alloc(void) | |
228 | { | |
229 | return kzalloc(sizeof(struct damon_sysfs_stats), GFP_KERNEL); | |
230 | } | |
231 | ||
232 | static ssize_t nr_tried_show(struct kobject *kobj, struct kobj_attribute *attr, | |
233 | char *buf) | |
234 | { | |
235 | struct damon_sysfs_stats *stats = container_of(kobj, | |
236 | struct damon_sysfs_stats, kobj); | |
237 | ||
238 | return sysfs_emit(buf, "%lu\n", stats->nr_tried); | |
239 | } | |
240 | ||
241 | static ssize_t sz_tried_show(struct kobject *kobj, struct kobj_attribute *attr, | |
242 | char *buf) | |
243 | { | |
244 | struct damon_sysfs_stats *stats = container_of(kobj, | |
245 | struct damon_sysfs_stats, kobj); | |
246 | ||
247 | return sysfs_emit(buf, "%lu\n", stats->sz_tried); | |
248 | } | |
249 | ||
250 | static ssize_t nr_applied_show(struct kobject *kobj, | |
251 | struct kobj_attribute *attr, char *buf) | |
252 | { | |
253 | struct damon_sysfs_stats *stats = container_of(kobj, | |
254 | struct damon_sysfs_stats, kobj); | |
255 | ||
256 | return sysfs_emit(buf, "%lu\n", stats->nr_applied); | |
257 | } | |
258 | ||
259 | static ssize_t sz_applied_show(struct kobject *kobj, | |
260 | struct kobj_attribute *attr, char *buf) | |
261 | { | |
262 | struct damon_sysfs_stats *stats = container_of(kobj, | |
263 | struct damon_sysfs_stats, kobj); | |
264 | ||
265 | return sysfs_emit(buf, "%lu\n", stats->sz_applied); | |
266 | } | |
267 | ||
268 | static ssize_t qt_exceeds_show(struct kobject *kobj, | |
269 | struct kobj_attribute *attr, char *buf) | |
270 | { | |
271 | struct damon_sysfs_stats *stats = container_of(kobj, | |
272 | struct damon_sysfs_stats, kobj); | |
273 | ||
274 | return sysfs_emit(buf, "%lu\n", stats->qt_exceeds); | |
275 | } | |
276 | ||
277 | static void damon_sysfs_stats_release(struct kobject *kobj) | |
278 | { | |
279 | kfree(container_of(kobj, struct damon_sysfs_stats, kobj)); | |
280 | } | |
281 | ||
282 | static struct kobj_attribute damon_sysfs_stats_nr_tried_attr = | |
283 | __ATTR_RO_MODE(nr_tried, 0400); | |
284 | ||
285 | static struct kobj_attribute damon_sysfs_stats_sz_tried_attr = | |
286 | __ATTR_RO_MODE(sz_tried, 0400); | |
287 | ||
288 | static struct kobj_attribute damon_sysfs_stats_nr_applied_attr = | |
289 | __ATTR_RO_MODE(nr_applied, 0400); | |
290 | ||
291 | static struct kobj_attribute damon_sysfs_stats_sz_applied_attr = | |
292 | __ATTR_RO_MODE(sz_applied, 0400); | |
293 | ||
294 | static struct kobj_attribute damon_sysfs_stats_qt_exceeds_attr = | |
295 | __ATTR_RO_MODE(qt_exceeds, 0400); | |
296 | ||
297 | static struct attribute *damon_sysfs_stats_attrs[] = { | |
298 | &damon_sysfs_stats_nr_tried_attr.attr, | |
299 | &damon_sysfs_stats_sz_tried_attr.attr, | |
300 | &damon_sysfs_stats_nr_applied_attr.attr, | |
301 | &damon_sysfs_stats_sz_applied_attr.attr, | |
302 | &damon_sysfs_stats_qt_exceeds_attr.attr, | |
303 | NULL, | |
304 | }; | |
305 | ATTRIBUTE_GROUPS(damon_sysfs_stats); | |
306 | ||
e56397e8 | 307 | static const struct kobj_type damon_sysfs_stats_ktype = { |
c8e7b4d0 SP |
308 | .release = damon_sysfs_stats_release, |
309 | .sysfs_ops = &kobj_sysfs_ops, | |
310 | .default_groups = damon_sysfs_stats_groups, | |
311 | }; | |
312 | ||
7ee161f1 SP |
313 | /* |
314 | * filter directory | |
315 | */ | |
316 | ||
317 | struct damon_sysfs_scheme_filter { | |
318 | struct kobject kobj; | |
319 | enum damos_filter_type type; | |
320 | bool matching; | |
321 | char *memcg_path; | |
2f1abcfc | 322 | struct damon_addr_range addr_range; |
9f6e47ab | 323 | int target_idx; |
7ee161f1 SP |
324 | }; |
325 | ||
472e2b70 SP |
326 | static struct damon_sysfs_scheme_filter *damon_sysfs_scheme_filter_alloc(void) |
327 | { | |
328 | return kzalloc(sizeof(struct damon_sysfs_scheme_filter), GFP_KERNEL); | |
329 | } | |
330 | ||
7ee161f1 SP |
331 | /* Should match with enum damos_filter_type */ |
332 | static const char * const damon_sysfs_scheme_filter_type_strs[] = { | |
333 | "anon", | |
334 | "memcg", | |
2f1abcfc | 335 | "addr", |
9f6e47ab | 336 | "target", |
7ee161f1 SP |
337 | }; |
338 | ||
339 | static ssize_t type_show(struct kobject *kobj, | |
340 | struct kobj_attribute *attr, char *buf) | |
341 | { | |
342 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
343 | struct damon_sysfs_scheme_filter, kobj); | |
344 | ||
345 | return sysfs_emit(buf, "%s\n", | |
346 | damon_sysfs_scheme_filter_type_strs[filter->type]); | |
347 | } | |
348 | ||
349 | static ssize_t type_store(struct kobject *kobj, | |
350 | struct kobj_attribute *attr, const char *buf, size_t count) | |
351 | { | |
352 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
353 | struct damon_sysfs_scheme_filter, kobj); | |
354 | enum damos_filter_type type; | |
355 | ssize_t ret = -EINVAL; | |
356 | ||
357 | for (type = 0; type < NR_DAMOS_FILTER_TYPES; type++) { | |
358 | if (sysfs_streq(buf, damon_sysfs_scheme_filter_type_strs[ | |
359 | type])) { | |
360 | filter->type = type; | |
361 | ret = count; | |
362 | break; | |
363 | } | |
364 | } | |
365 | return ret; | |
366 | } | |
367 | ||
368 | static ssize_t matching_show(struct kobject *kobj, | |
369 | struct kobj_attribute *attr, char *buf) | |
370 | { | |
371 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
372 | struct damon_sysfs_scheme_filter, kobj); | |
373 | ||
374 | return sysfs_emit(buf, "%c\n", filter->matching ? 'Y' : 'N'); | |
375 | } | |
376 | ||
377 | static ssize_t matching_store(struct kobject *kobj, | |
378 | struct kobj_attribute *attr, const char *buf, size_t count) | |
379 | { | |
380 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
381 | struct damon_sysfs_scheme_filter, kobj); | |
382 | bool matching; | |
383 | int err = kstrtobool(buf, &matching); | |
384 | ||
385 | if (err) | |
386 | return err; | |
387 | ||
388 | filter->matching = matching; | |
389 | return count; | |
390 | } | |
391 | ||
392 | static ssize_t memcg_path_show(struct kobject *kobj, | |
393 | struct kobj_attribute *attr, char *buf) | |
394 | { | |
395 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
396 | struct damon_sysfs_scheme_filter, kobj); | |
397 | ||
398 | return sysfs_emit(buf, "%s\n", | |
399 | filter->memcg_path ? filter->memcg_path : ""); | |
400 | } | |
401 | ||
402 | static ssize_t memcg_path_store(struct kobject *kobj, | |
403 | struct kobj_attribute *attr, const char *buf, size_t count) | |
404 | { | |
405 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
406 | struct damon_sysfs_scheme_filter, kobj); | |
407 | char *path = kmalloc(sizeof(*path) * (count + 1), GFP_KERNEL); | |
408 | ||
409 | if (!path) | |
410 | return -ENOMEM; | |
411 | ||
b6f00c91 | 412 | strscpy(path, buf, count + 1); |
7ee161f1 SP |
413 | filter->memcg_path = path; |
414 | return count; | |
415 | } | |
416 | ||
2f1abcfc SP |
417 | static ssize_t addr_start_show(struct kobject *kobj, |
418 | struct kobj_attribute *attr, char *buf) | |
419 | { | |
420 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
421 | struct damon_sysfs_scheme_filter, kobj); | |
422 | ||
423 | return sysfs_emit(buf, "%lu\n", filter->addr_range.start); | |
424 | } | |
425 | ||
426 | static ssize_t addr_start_store(struct kobject *kobj, | |
427 | struct kobj_attribute *attr, const char *buf, size_t count) | |
428 | { | |
429 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
430 | struct damon_sysfs_scheme_filter, kobj); | |
431 | int err = kstrtoul(buf, 0, &filter->addr_range.start); | |
432 | ||
433 | return err ? err : count; | |
434 | } | |
435 | ||
436 | static ssize_t addr_end_show(struct kobject *kobj, | |
437 | struct kobj_attribute *attr, char *buf) | |
438 | { | |
439 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
440 | struct damon_sysfs_scheme_filter, kobj); | |
441 | ||
442 | return sysfs_emit(buf, "%lu\n", filter->addr_range.end); | |
443 | } | |
444 | ||
445 | static ssize_t addr_end_store(struct kobject *kobj, | |
446 | struct kobj_attribute *attr, const char *buf, size_t count) | |
447 | { | |
448 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
449 | struct damon_sysfs_scheme_filter, kobj); | |
450 | int err = kstrtoul(buf, 0, &filter->addr_range.end); | |
451 | ||
452 | return err ? err : count; | |
453 | } | |
454 | ||
9f6e47ab SP |
455 | static ssize_t damon_target_idx_show(struct kobject *kobj, |
456 | struct kobj_attribute *attr, char *buf) | |
457 | { | |
458 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
459 | struct damon_sysfs_scheme_filter, kobj); | |
460 | ||
461 | return sysfs_emit(buf, "%d\n", filter->target_idx); | |
462 | } | |
463 | ||
464 | static ssize_t damon_target_idx_store(struct kobject *kobj, | |
465 | struct kobj_attribute *attr, const char *buf, size_t count) | |
466 | { | |
467 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
468 | struct damon_sysfs_scheme_filter, kobj); | |
469 | int err = kstrtoint(buf, 0, &filter->target_idx); | |
470 | ||
471 | return err ? err : count; | |
472 | } | |
473 | ||
7ee161f1 SP |
474 | static void damon_sysfs_scheme_filter_release(struct kobject *kobj) |
475 | { | |
476 | struct damon_sysfs_scheme_filter *filter = container_of(kobj, | |
477 | struct damon_sysfs_scheme_filter, kobj); | |
478 | ||
479 | kfree(filter->memcg_path); | |
480 | kfree(filter); | |
481 | } | |
482 | ||
483 | static struct kobj_attribute damon_sysfs_scheme_filter_type_attr = | |
484 | __ATTR_RW_MODE(type, 0600); | |
485 | ||
486 | static struct kobj_attribute damon_sysfs_scheme_filter_matching_attr = | |
487 | __ATTR_RW_MODE(matching, 0600); | |
488 | ||
489 | static struct kobj_attribute damon_sysfs_scheme_filter_memcg_path_attr = | |
490 | __ATTR_RW_MODE(memcg_path, 0600); | |
491 | ||
2f1abcfc SP |
492 | static struct kobj_attribute damon_sysfs_scheme_filter_addr_start_attr = |
493 | __ATTR_RW_MODE(addr_start, 0600); | |
494 | ||
495 | static struct kobj_attribute damon_sysfs_scheme_filter_addr_end_attr = | |
496 | __ATTR_RW_MODE(addr_end, 0600); | |
497 | ||
9f6e47ab SP |
498 | static struct kobj_attribute damon_sysfs_scheme_filter_damon_target_idx_attr = |
499 | __ATTR_RW_MODE(damon_target_idx, 0600); | |
500 | ||
7ee161f1 SP |
501 | static struct attribute *damon_sysfs_scheme_filter_attrs[] = { |
502 | &damon_sysfs_scheme_filter_type_attr.attr, | |
503 | &damon_sysfs_scheme_filter_matching_attr.attr, | |
504 | &damon_sysfs_scheme_filter_memcg_path_attr.attr, | |
2f1abcfc SP |
505 | &damon_sysfs_scheme_filter_addr_start_attr.attr, |
506 | &damon_sysfs_scheme_filter_addr_end_attr.attr, | |
9f6e47ab | 507 | &damon_sysfs_scheme_filter_damon_target_idx_attr.attr, |
7ee161f1 SP |
508 | NULL, |
509 | }; | |
510 | ATTRIBUTE_GROUPS(damon_sysfs_scheme_filter); | |
511 | ||
02cd4eb8 | 512 | static const struct kobj_type damon_sysfs_scheme_filter_ktype = { |
7ee161f1 SP |
513 | .release = damon_sysfs_scheme_filter_release, |
514 | .sysfs_ops = &kobj_sysfs_ops, | |
515 | .default_groups = damon_sysfs_scheme_filter_groups, | |
516 | }; | |
517 | ||
ac35264b SP |
518 | /* |
519 | * filters directory | |
520 | */ | |
521 | ||
522 | struct damon_sysfs_scheme_filters { | |
523 | struct kobject kobj; | |
472e2b70 | 524 | struct damon_sysfs_scheme_filter **filters_arr; |
ac35264b SP |
525 | int nr; |
526 | }; | |
527 | ||
528 | static struct damon_sysfs_scheme_filters * | |
529 | damon_sysfs_scheme_filters_alloc(void) | |
530 | { | |
531 | return kzalloc(sizeof(struct damon_sysfs_scheme_filters), GFP_KERNEL); | |
532 | } | |
533 | ||
472e2b70 SP |
534 | static void damon_sysfs_scheme_filters_rm_dirs( |
535 | struct damon_sysfs_scheme_filters *filters) | |
536 | { | |
537 | struct damon_sysfs_scheme_filter **filters_arr = filters->filters_arr; | |
538 | int i; | |
539 | ||
540 | for (i = 0; i < filters->nr; i++) | |
541 | kobject_put(&filters_arr[i]->kobj); | |
542 | filters->nr = 0; | |
543 | kfree(filters_arr); | |
544 | filters->filters_arr = NULL; | |
545 | } | |
546 | ||
547 | static int damon_sysfs_scheme_filters_add_dirs( | |
548 | struct damon_sysfs_scheme_filters *filters, int nr_filters) | |
549 | { | |
550 | struct damon_sysfs_scheme_filter **filters_arr, *filter; | |
551 | int err, i; | |
552 | ||
553 | damon_sysfs_scheme_filters_rm_dirs(filters); | |
554 | if (!nr_filters) | |
555 | return 0; | |
556 | ||
557 | filters_arr = kmalloc_array(nr_filters, sizeof(*filters_arr), | |
558 | GFP_KERNEL | __GFP_NOWARN); | |
559 | if (!filters_arr) | |
560 | return -ENOMEM; | |
561 | filters->filters_arr = filters_arr; | |
562 | ||
563 | for (i = 0; i < nr_filters; i++) { | |
564 | filter = damon_sysfs_scheme_filter_alloc(); | |
565 | if (!filter) { | |
566 | damon_sysfs_scheme_filters_rm_dirs(filters); | |
567 | return -ENOMEM; | |
568 | } | |
569 | ||
570 | err = kobject_init_and_add(&filter->kobj, | |
571 | &damon_sysfs_scheme_filter_ktype, | |
572 | &filters->kobj, "%d", i); | |
573 | if (err) { | |
574 | kobject_put(&filter->kobj); | |
575 | damon_sysfs_scheme_filters_rm_dirs(filters); | |
576 | return err; | |
577 | } | |
578 | ||
579 | filters_arr[i] = filter; | |
580 | filters->nr++; | |
581 | } | |
582 | return 0; | |
583 | } | |
584 | ||
ac35264b SP |
585 | static ssize_t nr_filters_show(struct kobject *kobj, |
586 | struct kobj_attribute *attr, char *buf) | |
587 | { | |
588 | struct damon_sysfs_scheme_filters *filters = container_of(kobj, | |
589 | struct damon_sysfs_scheme_filters, kobj); | |
590 | ||
591 | return sysfs_emit(buf, "%d\n", filters->nr); | |
592 | } | |
593 | ||
594 | static ssize_t nr_filters_store(struct kobject *kobj, | |
595 | struct kobj_attribute *attr, const char *buf, size_t count) | |
596 | { | |
472e2b70 | 597 | struct damon_sysfs_scheme_filters *filters; |
ac35264b SP |
598 | int nr, err = kstrtoint(buf, 0, &nr); |
599 | ||
600 | if (err) | |
601 | return err; | |
602 | if (nr < 0) | |
603 | return -EINVAL; | |
604 | ||
472e2b70 SP |
605 | filters = container_of(kobj, struct damon_sysfs_scheme_filters, kobj); |
606 | ||
607 | if (!mutex_trylock(&damon_sysfs_lock)) | |
608 | return -EBUSY; | |
609 | err = damon_sysfs_scheme_filters_add_dirs(filters, nr); | |
610 | mutex_unlock(&damon_sysfs_lock); | |
611 | if (err) | |
612 | return err; | |
613 | ||
ac35264b SP |
614 | return count; |
615 | } | |
616 | ||
617 | static void damon_sysfs_scheme_filters_release(struct kobject *kobj) | |
618 | { | |
619 | kfree(container_of(kobj, struct damon_sysfs_scheme_filters, kobj)); | |
620 | } | |
621 | ||
622 | static struct kobj_attribute damon_sysfs_scheme_filters_nr_attr = | |
623 | __ATTR_RW_MODE(nr_filters, 0600); | |
624 | ||
625 | static struct attribute *damon_sysfs_scheme_filters_attrs[] = { | |
626 | &damon_sysfs_scheme_filters_nr_attr.attr, | |
627 | NULL, | |
628 | }; | |
629 | ATTRIBUTE_GROUPS(damon_sysfs_scheme_filters); | |
630 | ||
02cd4eb8 | 631 | static const struct kobj_type damon_sysfs_scheme_filters_ktype = { |
ac35264b SP |
632 | .release = damon_sysfs_scheme_filters_release, |
633 | .sysfs_ops = &kobj_sysfs_ops, | |
634 | .default_groups = damon_sysfs_scheme_filters_groups, | |
635 | }; | |
636 | ||
c8e7b4d0 SP |
637 | /* |
638 | * watermarks directory | |
639 | */ | |
640 | ||
641 | struct damon_sysfs_watermarks { | |
642 | struct kobject kobj; | |
643 | enum damos_wmark_metric metric; | |
644 | unsigned long interval_us; | |
645 | unsigned long high; | |
646 | unsigned long mid; | |
647 | unsigned long low; | |
648 | }; | |
649 | ||
650 | static struct damon_sysfs_watermarks *damon_sysfs_watermarks_alloc( | |
651 | enum damos_wmark_metric metric, unsigned long interval_us, | |
652 | unsigned long high, unsigned long mid, unsigned long low) | |
653 | { | |
654 | struct damon_sysfs_watermarks *watermarks = kmalloc( | |
655 | sizeof(*watermarks), GFP_KERNEL); | |
656 | ||
657 | if (!watermarks) | |
658 | return NULL; | |
659 | watermarks->kobj = (struct kobject){}; | |
660 | watermarks->metric = metric; | |
661 | watermarks->interval_us = interval_us; | |
662 | watermarks->high = high; | |
663 | watermarks->mid = mid; | |
664 | watermarks->low = low; | |
665 | return watermarks; | |
666 | } | |
667 | ||
668 | /* Should match with enum damos_wmark_metric */ | |
669 | static const char * const damon_sysfs_wmark_metric_strs[] = { | |
670 | "none", | |
671 | "free_mem_rate", | |
672 | }; | |
673 | ||
674 | static ssize_t metric_show(struct kobject *kobj, struct kobj_attribute *attr, | |
675 | char *buf) | |
676 | { | |
677 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
678 | struct damon_sysfs_watermarks, kobj); | |
679 | ||
680 | return sysfs_emit(buf, "%s\n", | |
681 | damon_sysfs_wmark_metric_strs[watermarks->metric]); | |
682 | } | |
683 | ||
684 | static ssize_t metric_store(struct kobject *kobj, struct kobj_attribute *attr, | |
685 | const char *buf, size_t count) | |
686 | { | |
687 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
688 | struct damon_sysfs_watermarks, kobj); | |
689 | enum damos_wmark_metric metric; | |
690 | ||
691 | for (metric = 0; metric < NR_DAMOS_WMARK_METRICS; metric++) { | |
692 | if (sysfs_streq(buf, damon_sysfs_wmark_metric_strs[metric])) { | |
693 | watermarks->metric = metric; | |
694 | return count; | |
695 | } | |
696 | } | |
697 | return -EINVAL; | |
698 | } | |
699 | ||
700 | static ssize_t interval_us_show(struct kobject *kobj, | |
701 | struct kobj_attribute *attr, char *buf) | |
702 | { | |
703 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
704 | struct damon_sysfs_watermarks, kobj); | |
705 | ||
706 | return sysfs_emit(buf, "%lu\n", watermarks->interval_us); | |
707 | } | |
708 | ||
709 | static ssize_t interval_us_store(struct kobject *kobj, | |
710 | struct kobj_attribute *attr, const char *buf, size_t count) | |
711 | { | |
712 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
713 | struct damon_sysfs_watermarks, kobj); | |
714 | int err = kstrtoul(buf, 0, &watermarks->interval_us); | |
715 | ||
716 | return err ? err : count; | |
717 | } | |
718 | ||
719 | static ssize_t high_show(struct kobject *kobj, | |
720 | struct kobj_attribute *attr, char *buf) | |
721 | { | |
722 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
723 | struct damon_sysfs_watermarks, kobj); | |
724 | ||
725 | return sysfs_emit(buf, "%lu\n", watermarks->high); | |
726 | } | |
727 | ||
728 | static ssize_t high_store(struct kobject *kobj, | |
729 | struct kobj_attribute *attr, const char *buf, size_t count) | |
730 | { | |
731 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
732 | struct damon_sysfs_watermarks, kobj); | |
733 | int err = kstrtoul(buf, 0, &watermarks->high); | |
734 | ||
735 | return err ? err : count; | |
736 | } | |
737 | ||
738 | static ssize_t mid_show(struct kobject *kobj, | |
739 | struct kobj_attribute *attr, char *buf) | |
740 | { | |
741 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
742 | struct damon_sysfs_watermarks, kobj); | |
743 | ||
744 | return sysfs_emit(buf, "%lu\n", watermarks->mid); | |
745 | } | |
746 | ||
747 | static ssize_t mid_store(struct kobject *kobj, | |
748 | struct kobj_attribute *attr, const char *buf, size_t count) | |
749 | { | |
750 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
751 | struct damon_sysfs_watermarks, kobj); | |
752 | int err = kstrtoul(buf, 0, &watermarks->mid); | |
753 | ||
754 | return err ? err : count; | |
755 | } | |
756 | ||
757 | static ssize_t low_show(struct kobject *kobj, | |
758 | struct kobj_attribute *attr, char *buf) | |
759 | { | |
760 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
761 | struct damon_sysfs_watermarks, kobj); | |
762 | ||
763 | return sysfs_emit(buf, "%lu\n", watermarks->low); | |
764 | } | |
765 | ||
766 | static ssize_t low_store(struct kobject *kobj, | |
767 | struct kobj_attribute *attr, const char *buf, size_t count) | |
768 | { | |
769 | struct damon_sysfs_watermarks *watermarks = container_of(kobj, | |
770 | struct damon_sysfs_watermarks, kobj); | |
771 | int err = kstrtoul(buf, 0, &watermarks->low); | |
772 | ||
773 | return err ? err : count; | |
774 | } | |
775 | ||
776 | static void damon_sysfs_watermarks_release(struct kobject *kobj) | |
777 | { | |
778 | kfree(container_of(kobj, struct damon_sysfs_watermarks, kobj)); | |
779 | } | |
780 | ||
781 | static struct kobj_attribute damon_sysfs_watermarks_metric_attr = | |
782 | __ATTR_RW_MODE(metric, 0600); | |
783 | ||
784 | static struct kobj_attribute damon_sysfs_watermarks_interval_us_attr = | |
785 | __ATTR_RW_MODE(interval_us, 0600); | |
786 | ||
787 | static struct kobj_attribute damon_sysfs_watermarks_high_attr = | |
788 | __ATTR_RW_MODE(high, 0600); | |
789 | ||
790 | static struct kobj_attribute damon_sysfs_watermarks_mid_attr = | |
791 | __ATTR_RW_MODE(mid, 0600); | |
792 | ||
793 | static struct kobj_attribute damon_sysfs_watermarks_low_attr = | |
794 | __ATTR_RW_MODE(low, 0600); | |
795 | ||
796 | static struct attribute *damon_sysfs_watermarks_attrs[] = { | |
797 | &damon_sysfs_watermarks_metric_attr.attr, | |
798 | &damon_sysfs_watermarks_interval_us_attr.attr, | |
799 | &damon_sysfs_watermarks_high_attr.attr, | |
800 | &damon_sysfs_watermarks_mid_attr.attr, | |
801 | &damon_sysfs_watermarks_low_attr.attr, | |
802 | NULL, | |
803 | }; | |
804 | ATTRIBUTE_GROUPS(damon_sysfs_watermarks); | |
805 | ||
e56397e8 | 806 | static const struct kobj_type damon_sysfs_watermarks_ktype = { |
c8e7b4d0 SP |
807 | .release = damon_sysfs_watermarks_release, |
808 | .sysfs_ops = &kobj_sysfs_ops, | |
809 | .default_groups = damon_sysfs_watermarks_groups, | |
810 | }; | |
811 | ||
812 | /* | |
813 | * scheme/weights directory | |
814 | */ | |
815 | ||
816 | struct damon_sysfs_weights { | |
817 | struct kobject kobj; | |
818 | unsigned int sz; | |
819 | unsigned int nr_accesses; | |
820 | unsigned int age; | |
821 | }; | |
822 | ||
823 | static struct damon_sysfs_weights *damon_sysfs_weights_alloc(unsigned int sz, | |
824 | unsigned int nr_accesses, unsigned int age) | |
825 | { | |
826 | struct damon_sysfs_weights *weights = kmalloc(sizeof(*weights), | |
827 | GFP_KERNEL); | |
828 | ||
829 | if (!weights) | |
830 | return NULL; | |
831 | weights->kobj = (struct kobject){}; | |
832 | weights->sz = sz; | |
833 | weights->nr_accesses = nr_accesses; | |
834 | weights->age = age; | |
835 | return weights; | |
836 | } | |
837 | ||
838 | static ssize_t sz_permil_show(struct kobject *kobj, | |
839 | struct kobj_attribute *attr, char *buf) | |
840 | { | |
841 | struct damon_sysfs_weights *weights = container_of(kobj, | |
842 | struct damon_sysfs_weights, kobj); | |
843 | ||
844 | return sysfs_emit(buf, "%u\n", weights->sz); | |
845 | } | |
846 | ||
847 | static ssize_t sz_permil_store(struct kobject *kobj, | |
848 | struct kobj_attribute *attr, const char *buf, size_t count) | |
849 | { | |
850 | struct damon_sysfs_weights *weights = container_of(kobj, | |
851 | struct damon_sysfs_weights, kobj); | |
852 | int err = kstrtouint(buf, 0, &weights->sz); | |
853 | ||
854 | return err ? err : count; | |
855 | } | |
856 | ||
857 | static ssize_t nr_accesses_permil_show(struct kobject *kobj, | |
858 | struct kobj_attribute *attr, char *buf) | |
859 | { | |
860 | struct damon_sysfs_weights *weights = container_of(kobj, | |
861 | struct damon_sysfs_weights, kobj); | |
862 | ||
863 | return sysfs_emit(buf, "%u\n", weights->nr_accesses); | |
864 | } | |
865 | ||
866 | static ssize_t nr_accesses_permil_store(struct kobject *kobj, | |
867 | struct kobj_attribute *attr, const char *buf, size_t count) | |
868 | { | |
869 | struct damon_sysfs_weights *weights = container_of(kobj, | |
870 | struct damon_sysfs_weights, kobj); | |
871 | int err = kstrtouint(buf, 0, &weights->nr_accesses); | |
872 | ||
873 | return err ? err : count; | |
874 | } | |
875 | ||
876 | static ssize_t age_permil_show(struct kobject *kobj, | |
877 | struct kobj_attribute *attr, char *buf) | |
878 | { | |
879 | struct damon_sysfs_weights *weights = container_of(kobj, | |
880 | struct damon_sysfs_weights, kobj); | |
881 | ||
882 | return sysfs_emit(buf, "%u\n", weights->age); | |
883 | } | |
884 | ||
885 | static ssize_t age_permil_store(struct kobject *kobj, | |
886 | struct kobj_attribute *attr, const char *buf, size_t count) | |
887 | { | |
888 | struct damon_sysfs_weights *weights = container_of(kobj, | |
889 | struct damon_sysfs_weights, kobj); | |
890 | int err = kstrtouint(buf, 0, &weights->age); | |
891 | ||
892 | return err ? err : count; | |
893 | } | |
894 | ||
895 | static void damon_sysfs_weights_release(struct kobject *kobj) | |
896 | { | |
897 | kfree(container_of(kobj, struct damon_sysfs_weights, kobj)); | |
898 | } | |
899 | ||
900 | static struct kobj_attribute damon_sysfs_weights_sz_attr = | |
901 | __ATTR_RW_MODE(sz_permil, 0600); | |
902 | ||
903 | static struct kobj_attribute damon_sysfs_weights_nr_accesses_attr = | |
904 | __ATTR_RW_MODE(nr_accesses_permil, 0600); | |
905 | ||
906 | static struct kobj_attribute damon_sysfs_weights_age_attr = | |
907 | __ATTR_RW_MODE(age_permil, 0600); | |
908 | ||
909 | static struct attribute *damon_sysfs_weights_attrs[] = { | |
910 | &damon_sysfs_weights_sz_attr.attr, | |
911 | &damon_sysfs_weights_nr_accesses_attr.attr, | |
912 | &damon_sysfs_weights_age_attr.attr, | |
913 | NULL, | |
914 | }; | |
915 | ATTRIBUTE_GROUPS(damon_sysfs_weights); | |
916 | ||
e56397e8 | 917 | static const struct kobj_type damon_sysfs_weights_ktype = { |
c8e7b4d0 SP |
918 | .release = damon_sysfs_weights_release, |
919 | .sysfs_ops = &kobj_sysfs_ops, | |
920 | .default_groups = damon_sysfs_weights_groups, | |
921 | }; | |
922 | ||
923 | /* | |
924 | * quotas directory | |
925 | */ | |
926 | ||
927 | struct damon_sysfs_quotas { | |
928 | struct kobject kobj; | |
929 | struct damon_sysfs_weights *weights; | |
930 | unsigned long ms; | |
931 | unsigned long sz; | |
932 | unsigned long reset_interval_ms; | |
933 | }; | |
934 | ||
935 | static struct damon_sysfs_quotas *damon_sysfs_quotas_alloc(void) | |
936 | { | |
937 | return kzalloc(sizeof(struct damon_sysfs_quotas), GFP_KERNEL); | |
938 | } | |
939 | ||
940 | static int damon_sysfs_quotas_add_dirs(struct damon_sysfs_quotas *quotas) | |
941 | { | |
942 | struct damon_sysfs_weights *weights; | |
943 | int err; | |
944 | ||
945 | weights = damon_sysfs_weights_alloc(0, 0, 0); | |
946 | if (!weights) | |
947 | return -ENOMEM; | |
948 | ||
949 | err = kobject_init_and_add(&weights->kobj, &damon_sysfs_weights_ktype, | |
950 | "as->kobj, "weights"); | |
951 | if (err) | |
952 | kobject_put(&weights->kobj); | |
953 | else | |
954 | quotas->weights = weights; | |
955 | return err; | |
956 | } | |
957 | ||
958 | static void damon_sysfs_quotas_rm_dirs(struct damon_sysfs_quotas *quotas) | |
959 | { | |
960 | kobject_put("as->weights->kobj); | |
961 | } | |
962 | ||
963 | static ssize_t ms_show(struct kobject *kobj, struct kobj_attribute *attr, | |
964 | char *buf) | |
965 | { | |
966 | struct damon_sysfs_quotas *quotas = container_of(kobj, | |
967 | struct damon_sysfs_quotas, kobj); | |
968 | ||
969 | return sysfs_emit(buf, "%lu\n", quotas->ms); | |
970 | } | |
971 | ||
972 | static ssize_t ms_store(struct kobject *kobj, struct kobj_attribute *attr, | |
973 | const char *buf, size_t count) | |
974 | { | |
975 | struct damon_sysfs_quotas *quotas = container_of(kobj, | |
976 | struct damon_sysfs_quotas, kobj); | |
977 | int err = kstrtoul(buf, 0, "as->ms); | |
978 | ||
979 | if (err) | |
980 | return -EINVAL; | |
981 | return count; | |
982 | } | |
983 | ||
984 | static ssize_t bytes_show(struct kobject *kobj, struct kobj_attribute *attr, | |
985 | char *buf) | |
986 | { | |
987 | struct damon_sysfs_quotas *quotas = container_of(kobj, | |
988 | struct damon_sysfs_quotas, kobj); | |
989 | ||
990 | return sysfs_emit(buf, "%lu\n", quotas->sz); | |
991 | } | |
992 | ||
993 | static ssize_t bytes_store(struct kobject *kobj, | |
994 | struct kobj_attribute *attr, const char *buf, size_t count) | |
995 | { | |
996 | struct damon_sysfs_quotas *quotas = container_of(kobj, | |
997 | struct damon_sysfs_quotas, kobj); | |
998 | int err = kstrtoul(buf, 0, "as->sz); | |
999 | ||
1000 | if (err) | |
1001 | return -EINVAL; | |
1002 | return count; | |
1003 | } | |
1004 | ||
1005 | static ssize_t reset_interval_ms_show(struct kobject *kobj, | |
1006 | struct kobj_attribute *attr, char *buf) | |
1007 | { | |
1008 | struct damon_sysfs_quotas *quotas = container_of(kobj, | |
1009 | struct damon_sysfs_quotas, kobj); | |
1010 | ||
1011 | return sysfs_emit(buf, "%lu\n", quotas->reset_interval_ms); | |
1012 | } | |
1013 | ||
1014 | static ssize_t reset_interval_ms_store(struct kobject *kobj, | |
1015 | struct kobj_attribute *attr, const char *buf, size_t count) | |
1016 | { | |
1017 | struct damon_sysfs_quotas *quotas = container_of(kobj, | |
1018 | struct damon_sysfs_quotas, kobj); | |
1019 | int err = kstrtoul(buf, 0, "as->reset_interval_ms); | |
1020 | ||
1021 | if (err) | |
1022 | return -EINVAL; | |
1023 | return count; | |
1024 | } | |
1025 | ||
1026 | static void damon_sysfs_quotas_release(struct kobject *kobj) | |
1027 | { | |
1028 | kfree(container_of(kobj, struct damon_sysfs_quotas, kobj)); | |
1029 | } | |
1030 | ||
1031 | static struct kobj_attribute damon_sysfs_quotas_ms_attr = | |
1032 | __ATTR_RW_MODE(ms, 0600); | |
1033 | ||
1034 | static struct kobj_attribute damon_sysfs_quotas_sz_attr = | |
1035 | __ATTR_RW_MODE(bytes, 0600); | |
1036 | ||
1037 | static struct kobj_attribute damon_sysfs_quotas_reset_interval_ms_attr = | |
1038 | __ATTR_RW_MODE(reset_interval_ms, 0600); | |
1039 | ||
1040 | static struct attribute *damon_sysfs_quotas_attrs[] = { | |
1041 | &damon_sysfs_quotas_ms_attr.attr, | |
1042 | &damon_sysfs_quotas_sz_attr.attr, | |
1043 | &damon_sysfs_quotas_reset_interval_ms_attr.attr, | |
1044 | NULL, | |
1045 | }; | |
1046 | ATTRIBUTE_GROUPS(damon_sysfs_quotas); | |
1047 | ||
e56397e8 | 1048 | static const struct kobj_type damon_sysfs_quotas_ktype = { |
c8e7b4d0 SP |
1049 | .release = damon_sysfs_quotas_release, |
1050 | .sysfs_ops = &kobj_sysfs_ops, | |
1051 | .default_groups = damon_sysfs_quotas_groups, | |
1052 | }; | |
1053 | ||
1054 | /* | |
1055 | * access_pattern directory | |
1056 | */ | |
1057 | ||
1058 | struct damon_sysfs_access_pattern { | |
1059 | struct kobject kobj; | |
1060 | struct damon_sysfs_ul_range *sz; | |
1061 | struct damon_sysfs_ul_range *nr_accesses; | |
1062 | struct damon_sysfs_ul_range *age; | |
1063 | }; | |
1064 | ||
1065 | static | |
1066 | struct damon_sysfs_access_pattern *damon_sysfs_access_pattern_alloc(void) | |
1067 | { | |
1068 | struct damon_sysfs_access_pattern *access_pattern = | |
1069 | kmalloc(sizeof(*access_pattern), GFP_KERNEL); | |
1070 | ||
1071 | if (!access_pattern) | |
1072 | return NULL; | |
1073 | access_pattern->kobj = (struct kobject){}; | |
1074 | return access_pattern; | |
1075 | } | |
1076 | ||
1077 | static int damon_sysfs_access_pattern_add_range_dir( | |
1078 | struct damon_sysfs_access_pattern *access_pattern, | |
1079 | struct damon_sysfs_ul_range **range_dir_ptr, | |
1080 | char *name) | |
1081 | { | |
1082 | struct damon_sysfs_ul_range *range = damon_sysfs_ul_range_alloc(0, 0); | |
1083 | int err; | |
1084 | ||
1085 | if (!range) | |
1086 | return -ENOMEM; | |
1087 | err = kobject_init_and_add(&range->kobj, &damon_sysfs_ul_range_ktype, | |
1088 | &access_pattern->kobj, name); | |
1089 | if (err) | |
1090 | kobject_put(&range->kobj); | |
1091 | else | |
1092 | *range_dir_ptr = range; | |
1093 | return err; | |
1094 | } | |
1095 | ||
1096 | static int damon_sysfs_access_pattern_add_dirs( | |
1097 | struct damon_sysfs_access_pattern *access_pattern) | |
1098 | { | |
1099 | int err; | |
1100 | ||
1101 | err = damon_sysfs_access_pattern_add_range_dir(access_pattern, | |
1102 | &access_pattern->sz, "sz"); | |
1103 | if (err) | |
1104 | goto put_sz_out; | |
1105 | ||
1106 | err = damon_sysfs_access_pattern_add_range_dir(access_pattern, | |
1107 | &access_pattern->nr_accesses, "nr_accesses"); | |
1108 | if (err) | |
1109 | goto put_nr_accesses_sz_out; | |
1110 | ||
1111 | err = damon_sysfs_access_pattern_add_range_dir(access_pattern, | |
1112 | &access_pattern->age, "age"); | |
1113 | if (err) | |
1114 | goto put_age_nr_accesses_sz_out; | |
1115 | return 0; | |
1116 | ||
1117 | put_age_nr_accesses_sz_out: | |
1118 | kobject_put(&access_pattern->age->kobj); | |
1119 | access_pattern->age = NULL; | |
1120 | put_nr_accesses_sz_out: | |
1121 | kobject_put(&access_pattern->nr_accesses->kobj); | |
1122 | access_pattern->nr_accesses = NULL; | |
1123 | put_sz_out: | |
1124 | kobject_put(&access_pattern->sz->kobj); | |
1125 | access_pattern->sz = NULL; | |
1126 | return err; | |
1127 | } | |
1128 | ||
1129 | static void damon_sysfs_access_pattern_rm_dirs( | |
1130 | struct damon_sysfs_access_pattern *access_pattern) | |
1131 | { | |
1132 | kobject_put(&access_pattern->sz->kobj); | |
1133 | kobject_put(&access_pattern->nr_accesses->kobj); | |
1134 | kobject_put(&access_pattern->age->kobj); | |
1135 | } | |
1136 | ||
1137 | static void damon_sysfs_access_pattern_release(struct kobject *kobj) | |
1138 | { | |
1139 | kfree(container_of(kobj, struct damon_sysfs_access_pattern, kobj)); | |
1140 | } | |
1141 | ||
1142 | static struct attribute *damon_sysfs_access_pattern_attrs[] = { | |
1143 | NULL, | |
1144 | }; | |
1145 | ATTRIBUTE_GROUPS(damon_sysfs_access_pattern); | |
1146 | ||
e56397e8 | 1147 | static const struct kobj_type damon_sysfs_access_pattern_ktype = { |
c8e7b4d0 SP |
1148 | .release = damon_sysfs_access_pattern_release, |
1149 | .sysfs_ops = &kobj_sysfs_ops, | |
1150 | .default_groups = damon_sysfs_access_pattern_groups, | |
1151 | }; | |
1152 | ||
1153 | /* | |
1154 | * scheme directory | |
1155 | */ | |
1156 | ||
1157 | struct damon_sysfs_scheme { | |
1158 | struct kobject kobj; | |
1159 | enum damos_action action; | |
1160 | struct damon_sysfs_access_pattern *access_pattern; | |
a2a9f68e | 1161 | unsigned long apply_interval_us; |
c8e7b4d0 SP |
1162 | struct damon_sysfs_quotas *quotas; |
1163 | struct damon_sysfs_watermarks *watermarks; | |
ac35264b | 1164 | struct damon_sysfs_scheme_filters *filters; |
c8e7b4d0 | 1165 | struct damon_sysfs_stats *stats; |
5181b75f | 1166 | struct damon_sysfs_scheme_regions *tried_regions; |
c8e7b4d0 SP |
1167 | }; |
1168 | ||
1169 | /* This should match with enum damos_action */ | |
1170 | static const char * const damon_sysfs_damos_action_strs[] = { | |
1171 | "willneed", | |
1172 | "cold", | |
1173 | "pageout", | |
1174 | "hugepage", | |
1175 | "nohugepage", | |
1176 | "lru_prio", | |
1177 | "lru_deprio", | |
1178 | "stat", | |
1179 | }; | |
1180 | ||
1181 | static struct damon_sysfs_scheme *damon_sysfs_scheme_alloc( | |
a2a9f68e | 1182 | enum damos_action action, unsigned long apply_interval_us) |
c8e7b4d0 SP |
1183 | { |
1184 | struct damon_sysfs_scheme *scheme = kmalloc(sizeof(*scheme), | |
1185 | GFP_KERNEL); | |
1186 | ||
1187 | if (!scheme) | |
1188 | return NULL; | |
1189 | scheme->kobj = (struct kobject){}; | |
1190 | scheme->action = action; | |
a2a9f68e | 1191 | scheme->apply_interval_us = apply_interval_us; |
c8e7b4d0 SP |
1192 | return scheme; |
1193 | } | |
1194 | ||
1195 | static int damon_sysfs_scheme_set_access_pattern( | |
1196 | struct damon_sysfs_scheme *scheme) | |
1197 | { | |
1198 | struct damon_sysfs_access_pattern *access_pattern; | |
1199 | int err; | |
1200 | ||
1201 | access_pattern = damon_sysfs_access_pattern_alloc(); | |
1202 | if (!access_pattern) | |
1203 | return -ENOMEM; | |
1204 | err = kobject_init_and_add(&access_pattern->kobj, | |
1205 | &damon_sysfs_access_pattern_ktype, &scheme->kobj, | |
1206 | "access_pattern"); | |
1207 | if (err) | |
1208 | goto out; | |
1209 | err = damon_sysfs_access_pattern_add_dirs(access_pattern); | |
1210 | if (err) | |
1211 | goto out; | |
1212 | scheme->access_pattern = access_pattern; | |
1213 | return 0; | |
1214 | ||
1215 | out: | |
1216 | kobject_put(&access_pattern->kobj); | |
1217 | return err; | |
1218 | } | |
1219 | ||
1220 | static int damon_sysfs_scheme_set_quotas(struct damon_sysfs_scheme *scheme) | |
1221 | { | |
1222 | struct damon_sysfs_quotas *quotas = damon_sysfs_quotas_alloc(); | |
1223 | int err; | |
1224 | ||
1225 | if (!quotas) | |
1226 | return -ENOMEM; | |
1227 | err = kobject_init_and_add("as->kobj, &damon_sysfs_quotas_ktype, | |
1228 | &scheme->kobj, "quotas"); | |
1229 | if (err) | |
1230 | goto out; | |
1231 | err = damon_sysfs_quotas_add_dirs(quotas); | |
1232 | if (err) | |
1233 | goto out; | |
1234 | scheme->quotas = quotas; | |
1235 | return 0; | |
1236 | ||
1237 | out: | |
1238 | kobject_put("as->kobj); | |
1239 | return err; | |
1240 | } | |
1241 | ||
1242 | static int damon_sysfs_scheme_set_watermarks(struct damon_sysfs_scheme *scheme) | |
1243 | { | |
1244 | struct damon_sysfs_watermarks *watermarks = | |
1245 | damon_sysfs_watermarks_alloc(DAMOS_WMARK_NONE, 0, 0, 0, 0); | |
1246 | int err; | |
1247 | ||
1248 | if (!watermarks) | |
1249 | return -ENOMEM; | |
1250 | err = kobject_init_and_add(&watermarks->kobj, | |
1251 | &damon_sysfs_watermarks_ktype, &scheme->kobj, | |
1252 | "watermarks"); | |
1253 | if (err) | |
1254 | kobject_put(&watermarks->kobj); | |
1255 | else | |
1256 | scheme->watermarks = watermarks; | |
1257 | return err; | |
1258 | } | |
1259 | ||
ac35264b SP |
1260 | static int damon_sysfs_scheme_set_filters(struct damon_sysfs_scheme *scheme) |
1261 | { | |
1262 | struct damon_sysfs_scheme_filters *filters = | |
1263 | damon_sysfs_scheme_filters_alloc(); | |
1264 | int err; | |
1265 | ||
1266 | if (!filters) | |
1267 | return -ENOMEM; | |
1268 | err = kobject_init_and_add(&filters->kobj, | |
1269 | &damon_sysfs_scheme_filters_ktype, &scheme->kobj, | |
1270 | "filters"); | |
1271 | if (err) | |
1272 | kobject_put(&filters->kobj); | |
1273 | else | |
1274 | scheme->filters = filters; | |
1275 | return err; | |
1276 | } | |
1277 | ||
c8e7b4d0 SP |
1278 | static int damon_sysfs_scheme_set_stats(struct damon_sysfs_scheme *scheme) |
1279 | { | |
1280 | struct damon_sysfs_stats *stats = damon_sysfs_stats_alloc(); | |
1281 | int err; | |
1282 | ||
1283 | if (!stats) | |
1284 | return -ENOMEM; | |
1285 | err = kobject_init_and_add(&stats->kobj, &damon_sysfs_stats_ktype, | |
1286 | &scheme->kobj, "stats"); | |
1287 | if (err) | |
1288 | kobject_put(&stats->kobj); | |
1289 | else | |
1290 | scheme->stats = stats; | |
1291 | return err; | |
1292 | } | |
1293 | ||
5181b75f SP |
1294 | static int damon_sysfs_scheme_set_tried_regions( |
1295 | struct damon_sysfs_scheme *scheme) | |
1296 | { | |
1297 | struct damon_sysfs_scheme_regions *tried_regions = | |
1298 | damon_sysfs_scheme_regions_alloc(); | |
1299 | int err; | |
1300 | ||
1301 | if (!tried_regions) | |
1302 | return -ENOMEM; | |
1303 | err = kobject_init_and_add(&tried_regions->kobj, | |
1304 | &damon_sysfs_scheme_regions_ktype, &scheme->kobj, | |
1305 | "tried_regions"); | |
1306 | if (err) | |
1307 | kobject_put(&tried_regions->kobj); | |
1308 | else | |
1309 | scheme->tried_regions = tried_regions; | |
1310 | return err; | |
1311 | } | |
1312 | ||
c8e7b4d0 SP |
1313 | static int damon_sysfs_scheme_add_dirs(struct damon_sysfs_scheme *scheme) |
1314 | { | |
1315 | int err; | |
1316 | ||
1317 | err = damon_sysfs_scheme_set_access_pattern(scheme); | |
1318 | if (err) | |
1319 | return err; | |
1320 | err = damon_sysfs_scheme_set_quotas(scheme); | |
1321 | if (err) | |
1322 | goto put_access_pattern_out; | |
1323 | err = damon_sysfs_scheme_set_watermarks(scheme); | |
1324 | if (err) | |
1325 | goto put_quotas_access_pattern_out; | |
ac35264b | 1326 | err = damon_sysfs_scheme_set_filters(scheme); |
c8e7b4d0 SP |
1327 | if (err) |
1328 | goto put_watermarks_quotas_access_pattern_out; | |
ac35264b SP |
1329 | err = damon_sysfs_scheme_set_stats(scheme); |
1330 | if (err) | |
1331 | goto put_filters_watermarks_quotas_access_pattern_out; | |
5181b75f SP |
1332 | err = damon_sysfs_scheme_set_tried_regions(scheme); |
1333 | if (err) | |
1334 | goto put_tried_regions_out; | |
c8e7b4d0 SP |
1335 | return 0; |
1336 | ||
5181b75f SP |
1337 | put_tried_regions_out: |
1338 | kobject_put(&scheme->tried_regions->kobj); | |
1339 | scheme->tried_regions = NULL; | |
ac35264b SP |
1340 | put_filters_watermarks_quotas_access_pattern_out: |
1341 | kobject_put(&scheme->filters->kobj); | |
1342 | scheme->filters = NULL; | |
c8e7b4d0 SP |
1343 | put_watermarks_quotas_access_pattern_out: |
1344 | kobject_put(&scheme->watermarks->kobj); | |
1345 | scheme->watermarks = NULL; | |
1346 | put_quotas_access_pattern_out: | |
1347 | kobject_put(&scheme->quotas->kobj); | |
1348 | scheme->quotas = NULL; | |
1349 | put_access_pattern_out: | |
1350 | kobject_put(&scheme->access_pattern->kobj); | |
1351 | scheme->access_pattern = NULL; | |
1352 | return err; | |
1353 | } | |
1354 | ||
1355 | static void damon_sysfs_scheme_rm_dirs(struct damon_sysfs_scheme *scheme) | |
1356 | { | |
1357 | damon_sysfs_access_pattern_rm_dirs(scheme->access_pattern); | |
1358 | kobject_put(&scheme->access_pattern->kobj); | |
1359 | damon_sysfs_quotas_rm_dirs(scheme->quotas); | |
1360 | kobject_put(&scheme->quotas->kobj); | |
1361 | kobject_put(&scheme->watermarks->kobj); | |
472e2b70 | 1362 | damon_sysfs_scheme_filters_rm_dirs(scheme->filters); |
ac35264b | 1363 | kobject_put(&scheme->filters->kobj); |
c8e7b4d0 | 1364 | kobject_put(&scheme->stats->kobj); |
9277d036 | 1365 | damon_sysfs_scheme_regions_rm_dirs(scheme->tried_regions); |
5181b75f | 1366 | kobject_put(&scheme->tried_regions->kobj); |
c8e7b4d0 SP |
1367 | } |
1368 | ||
1369 | static ssize_t action_show(struct kobject *kobj, struct kobj_attribute *attr, | |
1370 | char *buf) | |
1371 | { | |
1372 | struct damon_sysfs_scheme *scheme = container_of(kobj, | |
1373 | struct damon_sysfs_scheme, kobj); | |
1374 | ||
1375 | return sysfs_emit(buf, "%s\n", | |
1376 | damon_sysfs_damos_action_strs[scheme->action]); | |
1377 | } | |
1378 | ||
1379 | static ssize_t action_store(struct kobject *kobj, struct kobj_attribute *attr, | |
1380 | const char *buf, size_t count) | |
1381 | { | |
1382 | struct damon_sysfs_scheme *scheme = container_of(kobj, | |
1383 | struct damon_sysfs_scheme, kobj); | |
1384 | enum damos_action action; | |
1385 | ||
1386 | for (action = 0; action < NR_DAMOS_ACTIONS; action++) { | |
1387 | if (sysfs_streq(buf, damon_sysfs_damos_action_strs[action])) { | |
1388 | scheme->action = action; | |
1389 | return count; | |
1390 | } | |
1391 | } | |
1392 | return -EINVAL; | |
1393 | } | |
1394 | ||
a2a9f68e SP |
1395 | static ssize_t apply_interval_us_show(struct kobject *kobj, |
1396 | struct kobj_attribute *attr, char *buf) | |
1397 | { | |
1398 | struct damon_sysfs_scheme *scheme = container_of(kobj, | |
1399 | struct damon_sysfs_scheme, kobj); | |
1400 | ||
1401 | return sysfs_emit(buf, "%lu\n", scheme->apply_interval_us); | |
1402 | } | |
1403 | ||
1404 | static ssize_t apply_interval_us_store(struct kobject *kobj, | |
1405 | struct kobj_attribute *attr, const char *buf, size_t count) | |
1406 | { | |
1407 | struct damon_sysfs_scheme *scheme = container_of(kobj, | |
1408 | struct damon_sysfs_scheme, kobj); | |
1409 | int err = kstrtoul(buf, 0, &scheme->apply_interval_us); | |
1410 | ||
1411 | return err ? err : count; | |
1412 | } | |
1413 | ||
c8e7b4d0 SP |
1414 | static void damon_sysfs_scheme_release(struct kobject *kobj) |
1415 | { | |
1416 | kfree(container_of(kobj, struct damon_sysfs_scheme, kobj)); | |
1417 | } | |
1418 | ||
1419 | static struct kobj_attribute damon_sysfs_scheme_action_attr = | |
1420 | __ATTR_RW_MODE(action, 0600); | |
1421 | ||
a2a9f68e SP |
1422 | static struct kobj_attribute damon_sysfs_scheme_apply_interval_us_attr = |
1423 | __ATTR_RW_MODE(apply_interval_us, 0600); | |
1424 | ||
c8e7b4d0 SP |
1425 | static struct attribute *damon_sysfs_scheme_attrs[] = { |
1426 | &damon_sysfs_scheme_action_attr.attr, | |
a2a9f68e | 1427 | &damon_sysfs_scheme_apply_interval_us_attr.attr, |
c8e7b4d0 SP |
1428 | NULL, |
1429 | }; | |
1430 | ATTRIBUTE_GROUPS(damon_sysfs_scheme); | |
1431 | ||
e56397e8 | 1432 | static const struct kobj_type damon_sysfs_scheme_ktype = { |
c8e7b4d0 SP |
1433 | .release = damon_sysfs_scheme_release, |
1434 | .sysfs_ops = &kobj_sysfs_ops, | |
1435 | .default_groups = damon_sysfs_scheme_groups, | |
1436 | }; | |
1437 | ||
1438 | /* | |
1439 | * schemes directory | |
1440 | */ | |
1441 | ||
1442 | struct damon_sysfs_schemes *damon_sysfs_schemes_alloc(void) | |
1443 | { | |
1444 | return kzalloc(sizeof(struct damon_sysfs_schemes), GFP_KERNEL); | |
1445 | } | |
1446 | ||
1447 | void damon_sysfs_schemes_rm_dirs(struct damon_sysfs_schemes *schemes) | |
1448 | { | |
1449 | struct damon_sysfs_scheme **schemes_arr = schemes->schemes_arr; | |
1450 | int i; | |
1451 | ||
1452 | for (i = 0; i < schemes->nr; i++) { | |
1453 | damon_sysfs_scheme_rm_dirs(schemes_arr[i]); | |
1454 | kobject_put(&schemes_arr[i]->kobj); | |
1455 | } | |
1456 | schemes->nr = 0; | |
1457 | kfree(schemes_arr); | |
1458 | schemes->schemes_arr = NULL; | |
1459 | } | |
1460 | ||
1461 | static int damon_sysfs_schemes_add_dirs(struct damon_sysfs_schemes *schemes, | |
1462 | int nr_schemes) | |
1463 | { | |
1464 | struct damon_sysfs_scheme **schemes_arr, *scheme; | |
1465 | int err, i; | |
1466 | ||
1467 | damon_sysfs_schemes_rm_dirs(schemes); | |
1468 | if (!nr_schemes) | |
1469 | return 0; | |
1470 | ||
1471 | schemes_arr = kmalloc_array(nr_schemes, sizeof(*schemes_arr), | |
1472 | GFP_KERNEL | __GFP_NOWARN); | |
1473 | if (!schemes_arr) | |
1474 | return -ENOMEM; | |
1475 | schemes->schemes_arr = schemes_arr; | |
1476 | ||
1477 | for (i = 0; i < nr_schemes; i++) { | |
a2a9f68e SP |
1478 | /* |
1479 | * apply_interval_us as 0 means same to aggregation interval | |
1480 | * (same to before-apply_interval behavior) | |
1481 | */ | |
1482 | scheme = damon_sysfs_scheme_alloc(DAMOS_STAT, 0); | |
c8e7b4d0 SP |
1483 | if (!scheme) { |
1484 | damon_sysfs_schemes_rm_dirs(schemes); | |
1485 | return -ENOMEM; | |
1486 | } | |
1487 | ||
1488 | err = kobject_init_and_add(&scheme->kobj, | |
1489 | &damon_sysfs_scheme_ktype, &schemes->kobj, | |
1490 | "%d", i); | |
1491 | if (err) | |
1492 | goto out; | |
1493 | err = damon_sysfs_scheme_add_dirs(scheme); | |
1494 | if (err) | |
1495 | goto out; | |
1496 | ||
1497 | schemes_arr[i] = scheme; | |
1498 | schemes->nr++; | |
1499 | } | |
1500 | return 0; | |
1501 | ||
1502 | out: | |
1503 | damon_sysfs_schemes_rm_dirs(schemes); | |
1504 | kobject_put(&scheme->kobj); | |
1505 | return err; | |
1506 | } | |
1507 | ||
1508 | static ssize_t nr_schemes_show(struct kobject *kobj, | |
1509 | struct kobj_attribute *attr, char *buf) | |
1510 | { | |
1511 | struct damon_sysfs_schemes *schemes = container_of(kobj, | |
1512 | struct damon_sysfs_schemes, kobj); | |
1513 | ||
1514 | return sysfs_emit(buf, "%d\n", schemes->nr); | |
1515 | } | |
1516 | ||
1517 | static ssize_t nr_schemes_store(struct kobject *kobj, | |
1518 | struct kobj_attribute *attr, const char *buf, size_t count) | |
1519 | { | |
1520 | struct damon_sysfs_schemes *schemes; | |
1521 | int nr, err = kstrtoint(buf, 0, &nr); | |
1522 | ||
1523 | if (err) | |
1524 | return err; | |
1525 | if (nr < 0) | |
1526 | return -EINVAL; | |
1527 | ||
1528 | schemes = container_of(kobj, struct damon_sysfs_schemes, kobj); | |
1529 | ||
1530 | if (!mutex_trylock(&damon_sysfs_lock)) | |
1531 | return -EBUSY; | |
1532 | err = damon_sysfs_schemes_add_dirs(schemes, nr); | |
1533 | mutex_unlock(&damon_sysfs_lock); | |
1534 | if (err) | |
1535 | return err; | |
1536 | return count; | |
1537 | } | |
1538 | ||
1539 | static void damon_sysfs_schemes_release(struct kobject *kobj) | |
1540 | { | |
1541 | kfree(container_of(kobj, struct damon_sysfs_schemes, kobj)); | |
1542 | } | |
1543 | ||
1544 | static struct kobj_attribute damon_sysfs_schemes_nr_attr = | |
1545 | __ATTR_RW_MODE(nr_schemes, 0600); | |
1546 | ||
1547 | static struct attribute *damon_sysfs_schemes_attrs[] = { | |
1548 | &damon_sysfs_schemes_nr_attr.attr, | |
1549 | NULL, | |
1550 | }; | |
1551 | ATTRIBUTE_GROUPS(damon_sysfs_schemes); | |
1552 | ||
e56397e8 | 1553 | const struct kobj_type damon_sysfs_schemes_ktype = { |
c8e7b4d0 SP |
1554 | .release = damon_sysfs_schemes_release, |
1555 | .sysfs_ops = &kobj_sysfs_ops, | |
1556 | .default_groups = damon_sysfs_schemes_groups, | |
1557 | }; | |
1558 | ||
29cbb9a1 SP |
1559 | static bool damon_sysfs_memcg_path_eq(struct mem_cgroup *memcg, |
1560 | char *memcg_path_buf, char *path) | |
1561 | { | |
1562 | #ifdef CONFIG_MEMCG | |
1563 | cgroup_path(memcg->css.cgroup, memcg_path_buf, PATH_MAX); | |
1564 | if (sysfs_streq(memcg_path_buf, path)) | |
1565 | return true; | |
1566 | #endif /* CONFIG_MEMCG */ | |
1567 | return false; | |
1568 | } | |
1569 | ||
1570 | static int damon_sysfs_memcg_path_to_id(char *memcg_path, unsigned short *id) | |
1571 | { | |
1572 | struct mem_cgroup *memcg; | |
1573 | char *path; | |
1574 | bool found = false; | |
1575 | ||
1576 | if (!memcg_path) | |
1577 | return -EINVAL; | |
1578 | ||
1579 | path = kmalloc(sizeof(*path) * PATH_MAX, GFP_KERNEL); | |
1580 | if (!path) | |
1581 | return -ENOMEM; | |
1582 | ||
1583 | for (memcg = mem_cgroup_iter(NULL, NULL, NULL); memcg; | |
1584 | memcg = mem_cgroup_iter(NULL, memcg, NULL)) { | |
1585 | /* skip removed memcg */ | |
1586 | if (!mem_cgroup_id(memcg)) | |
1587 | continue; | |
1588 | if (damon_sysfs_memcg_path_eq(memcg, path, memcg_path)) { | |
1589 | *id = mem_cgroup_id(memcg); | |
1590 | found = true; | |
1591 | break; | |
1592 | } | |
1593 | } | |
1594 | ||
1595 | kfree(path); | |
1596 | return found ? 0 : -EINVAL; | |
1597 | } | |
1598 | ||
1599 | static int damon_sysfs_set_scheme_filters(struct damos *scheme, | |
1600 | struct damon_sysfs_scheme_filters *sysfs_filters) | |
1601 | { | |
1602 | int i; | |
1603 | struct damos_filter *filter, *next; | |
1604 | ||
1605 | damos_for_each_filter_safe(filter, next, scheme) | |
1606 | damos_destroy_filter(filter); | |
1607 | ||
1608 | for (i = 0; i < sysfs_filters->nr; i++) { | |
1609 | struct damon_sysfs_scheme_filter *sysfs_filter = | |
1610 | sysfs_filters->filters_arr[i]; | |
1611 | struct damos_filter *filter = | |
1612 | damos_new_filter(sysfs_filter->type, | |
1613 | sysfs_filter->matching); | |
1614 | int err; | |
1615 | ||
1616 | if (!filter) | |
1617 | return -ENOMEM; | |
1618 | if (filter->type == DAMOS_FILTER_TYPE_MEMCG) { | |
1619 | err = damon_sysfs_memcg_path_to_id( | |
1620 | sysfs_filter->memcg_path, | |
1621 | &filter->memcg_id); | |
1622 | if (err) { | |
1623 | damos_destroy_filter(filter); | |
1624 | return err; | |
1625 | } | |
2f1abcfc SP |
1626 | } else if (filter->type == DAMOS_FILTER_TYPE_ADDR) { |
1627 | if (sysfs_filter->addr_range.end < | |
1628 | sysfs_filter->addr_range.start) { | |
1629 | damos_destroy_filter(filter); | |
1630 | return -EINVAL; | |
1631 | } | |
1632 | filter->addr_range = sysfs_filter->addr_range; | |
9f6e47ab SP |
1633 | } else if (filter->type == DAMOS_FILTER_TYPE_TARGET) { |
1634 | filter->target_idx = sysfs_filter->target_idx; | |
29cbb9a1 | 1635 | } |
2f1abcfc | 1636 | |
29cbb9a1 SP |
1637 | damos_add_filter(scheme, filter); |
1638 | } | |
1639 | return 0; | |
1640 | } | |
1641 | ||
c8e7b4d0 SP |
1642 | static struct damos *damon_sysfs_mk_scheme( |
1643 | struct damon_sysfs_scheme *sysfs_scheme) | |
1644 | { | |
1645 | struct damon_sysfs_access_pattern *access_pattern = | |
1646 | sysfs_scheme->access_pattern; | |
1647 | struct damon_sysfs_quotas *sysfs_quotas = sysfs_scheme->quotas; | |
1648 | struct damon_sysfs_weights *sysfs_weights = sysfs_quotas->weights; | |
1649 | struct damon_sysfs_watermarks *sysfs_wmarks = sysfs_scheme->watermarks; | |
29cbb9a1 SP |
1650 | struct damon_sysfs_scheme_filters *sysfs_filters = |
1651 | sysfs_scheme->filters; | |
1652 | struct damos *scheme; | |
1653 | int err; | |
c8e7b4d0 SP |
1654 | |
1655 | struct damos_access_pattern pattern = { | |
1656 | .min_sz_region = access_pattern->sz->min, | |
1657 | .max_sz_region = access_pattern->sz->max, | |
1658 | .min_nr_accesses = access_pattern->nr_accesses->min, | |
1659 | .max_nr_accesses = access_pattern->nr_accesses->max, | |
1660 | .min_age_region = access_pattern->age->min, | |
1661 | .max_age_region = access_pattern->age->max, | |
1662 | }; | |
1663 | struct damos_quota quota = { | |
1664 | .ms = sysfs_quotas->ms, | |
1665 | .sz = sysfs_quotas->sz, | |
1666 | .reset_interval = sysfs_quotas->reset_interval_ms, | |
1667 | .weight_sz = sysfs_weights->sz, | |
1668 | .weight_nr_accesses = sysfs_weights->nr_accesses, | |
1669 | .weight_age = sysfs_weights->age, | |
1670 | }; | |
1671 | struct damos_watermarks wmarks = { | |
1672 | .metric = sysfs_wmarks->metric, | |
1673 | .interval = sysfs_wmarks->interval_us, | |
1674 | .high = sysfs_wmarks->high, | |
1675 | .mid = sysfs_wmarks->mid, | |
1676 | .low = sysfs_wmarks->low, | |
1677 | }; | |
1678 | ||
a2a9f68e SP |
1679 | scheme = damon_new_scheme(&pattern, sysfs_scheme->action, |
1680 | sysfs_scheme->apply_interval_us, "a, &wmarks); | |
29cbb9a1 SP |
1681 | if (!scheme) |
1682 | return NULL; | |
1683 | ||
1684 | err = damon_sysfs_set_scheme_filters(scheme, sysfs_filters); | |
1685 | if (err) { | |
1686 | damon_destroy_scheme(scheme); | |
1687 | return NULL; | |
1688 | } | |
1689 | return scheme; | |
c8e7b4d0 SP |
1690 | } |
1691 | ||
1692 | static void damon_sysfs_update_scheme(struct damos *scheme, | |
1693 | struct damon_sysfs_scheme *sysfs_scheme) | |
1694 | { | |
1695 | struct damon_sysfs_access_pattern *access_pattern = | |
1696 | sysfs_scheme->access_pattern; | |
1697 | struct damon_sysfs_quotas *sysfs_quotas = sysfs_scheme->quotas; | |
1698 | struct damon_sysfs_weights *sysfs_weights = sysfs_quotas->weights; | |
1699 | struct damon_sysfs_watermarks *sysfs_wmarks = sysfs_scheme->watermarks; | |
29cbb9a1 | 1700 | int err; |
c8e7b4d0 SP |
1701 | |
1702 | scheme->pattern.min_sz_region = access_pattern->sz->min; | |
1703 | scheme->pattern.max_sz_region = access_pattern->sz->max; | |
1704 | scheme->pattern.min_nr_accesses = access_pattern->nr_accesses->min; | |
1705 | scheme->pattern.max_nr_accesses = access_pattern->nr_accesses->max; | |
1706 | scheme->pattern.min_age_region = access_pattern->age->min; | |
1707 | scheme->pattern.max_age_region = access_pattern->age->max; | |
1708 | ||
1709 | scheme->action = sysfs_scheme->action; | |
a2a9f68e | 1710 | scheme->apply_interval_us = sysfs_scheme->apply_interval_us; |
c8e7b4d0 SP |
1711 | |
1712 | scheme->quota.ms = sysfs_quotas->ms; | |
1713 | scheme->quota.sz = sysfs_quotas->sz; | |
1714 | scheme->quota.reset_interval = sysfs_quotas->reset_interval_ms; | |
1715 | scheme->quota.weight_sz = sysfs_weights->sz; | |
1716 | scheme->quota.weight_nr_accesses = sysfs_weights->nr_accesses; | |
1717 | scheme->quota.weight_age = sysfs_weights->age; | |
1718 | ||
1719 | scheme->wmarks.metric = sysfs_wmarks->metric; | |
1720 | scheme->wmarks.interval = sysfs_wmarks->interval_us; | |
1721 | scheme->wmarks.high = sysfs_wmarks->high; | |
1722 | scheme->wmarks.mid = sysfs_wmarks->mid; | |
1723 | scheme->wmarks.low = sysfs_wmarks->low; | |
29cbb9a1 SP |
1724 | |
1725 | err = damon_sysfs_set_scheme_filters(scheme, sysfs_scheme->filters); | |
1726 | if (err) | |
1727 | damon_destroy_scheme(scheme); | |
c8e7b4d0 SP |
1728 | } |
1729 | ||
1730 | int damon_sysfs_set_schemes(struct damon_ctx *ctx, | |
1731 | struct damon_sysfs_schemes *sysfs_schemes) | |
1732 | { | |
1733 | struct damos *scheme, *next; | |
1734 | int i = 0; | |
1735 | ||
1736 | damon_for_each_scheme_safe(scheme, next, ctx) { | |
1737 | if (i < sysfs_schemes->nr) | |
1738 | damon_sysfs_update_scheme(scheme, | |
1739 | sysfs_schemes->schemes_arr[i]); | |
1740 | else | |
1741 | damon_destroy_scheme(scheme); | |
1742 | i++; | |
1743 | } | |
1744 | ||
1745 | for (; i < sysfs_schemes->nr; i++) { | |
1746 | struct damos *scheme, *next; | |
1747 | ||
1748 | scheme = damon_sysfs_mk_scheme(sysfs_schemes->schemes_arr[i]); | |
1749 | if (!scheme) { | |
1750 | damon_for_each_scheme_safe(scheme, next, ctx) | |
1751 | damon_destroy_scheme(scheme); | |
1752 | return -ENOMEM; | |
1753 | } | |
1754 | damon_add_scheme(ctx, scheme); | |
1755 | } | |
1756 | return 0; | |
1757 | } | |
1758 | ||
1759 | void damon_sysfs_schemes_update_stats( | |
1760 | struct damon_sysfs_schemes *sysfs_schemes, | |
1761 | struct damon_ctx *ctx) | |
1762 | { | |
1763 | struct damos *scheme; | |
1764 | int schemes_idx = 0; | |
1765 | ||
1766 | damon_for_each_scheme(scheme, ctx) { | |
1767 | struct damon_sysfs_stats *sysfs_stats; | |
1768 | ||
1769 | /* user could have removed the scheme sysfs dir */ | |
1770 | if (schemes_idx >= sysfs_schemes->nr) | |
1771 | break; | |
1772 | ||
1773 | sysfs_stats = sysfs_schemes->schemes_arr[schemes_idx++]->stats; | |
1774 | sysfs_stats->nr_tried = scheme->stat.nr_tried; | |
1775 | sysfs_stats->sz_tried = scheme->stat.sz_tried; | |
1776 | sysfs_stats->nr_applied = scheme->stat.nr_applied; | |
1777 | sysfs_stats->sz_applied = scheme->stat.sz_applied; | |
1778 | sysfs_stats->qt_exceeds = scheme->stat.qt_exceeds; | |
1779 | } | |
1780 | } | |
f1d13cac SP |
1781 | |
1782 | /* | |
1783 | * damon_sysfs_schemes that need to update its schemes regions dir. Protected | |
1784 | * by damon_sysfs_lock | |
1785 | */ | |
1786 | static struct damon_sysfs_schemes *damon_sysfs_schemes_for_damos_callback; | |
1787 | static int damon_sysfs_schemes_region_idx; | |
6ad243b8 | 1788 | static bool damos_regions_upd_total_bytes_only; |
f1d13cac SP |
1789 | |
1790 | /* | |
1791 | * DAMON callback that called before damos apply. While this callback is | |
1792 | * registered, damon_sysfs_lock should be held to ensure the regions | |
1793 | * directories exist. | |
1794 | */ | |
1795 | static int damon_sysfs_before_damos_apply(struct damon_ctx *ctx, | |
1796 | struct damon_target *t, struct damon_region *r, | |
1797 | struct damos *s) | |
1798 | { | |
1799 | struct damos *scheme; | |
1800 | struct damon_sysfs_scheme_regions *sysfs_regions; | |
1801 | struct damon_sysfs_scheme_region *region; | |
1802 | struct damon_sysfs_schemes *sysfs_schemes = | |
1803 | damon_sysfs_schemes_for_damos_callback; | |
1804 | int schemes_idx = 0; | |
1805 | ||
1806 | damon_for_each_scheme(scheme, ctx) { | |
1807 | if (scheme == s) | |
1808 | break; | |
1809 | schemes_idx++; | |
1810 | } | |
1811 | ||
1812 | /* user could have removed the scheme sysfs dir */ | |
1813 | if (schemes_idx >= sysfs_schemes->nr) | |
1814 | return 0; | |
1815 | ||
1816 | sysfs_regions = sysfs_schemes->schemes_arr[schemes_idx]->tried_regions; | |
4d4e41b6 SP |
1817 | if (sysfs_regions->upd_status == DAMOS_TRIED_REGIONS_UPD_FINISHED) |
1818 | return 0; | |
1819 | if (sysfs_regions->upd_status == DAMOS_TRIED_REGIONS_UPD_IDLE) | |
1820 | sysfs_regions->upd_status = DAMOS_TRIED_REGIONS_UPD_STARTED; | |
b69f92a7 | 1821 | sysfs_regions->total_bytes += r->ar.end - r->ar.start; |
6ad243b8 SP |
1822 | if (damos_regions_upd_total_bytes_only) |
1823 | return 0; | |
1824 | ||
f1d13cac SP |
1825 | region = damon_sysfs_scheme_region_alloc(r); |
1826 | list_add_tail(®ion->list, &sysfs_regions->regions_list); | |
1827 | sysfs_regions->nr_regions++; | |
1828 | if (kobject_init_and_add(®ion->kobj, | |
1829 | &damon_sysfs_scheme_region_ktype, | |
1830 | &sysfs_regions->kobj, "%d", | |
1831 | damon_sysfs_schemes_region_idx++)) { | |
1832 | kobject_put(®ion->kobj); | |
1833 | } | |
1834 | return 0; | |
1835 | } | |
1836 | ||
4d4e41b6 SP |
1837 | /* |
1838 | * DAMON callback that called after each accesses sampling. While this | |
1839 | * callback is registered, damon_sysfs_lock should be held to ensure the | |
1840 | * regions directories exist. | |
1841 | */ | |
1842 | static int damon_sysfs_after_sampling(struct damon_ctx *ctx) | |
1843 | { | |
1844 | struct damon_sysfs_schemes *sysfs_schemes = | |
1845 | damon_sysfs_schemes_for_damos_callback; | |
1846 | struct damon_sysfs_scheme_regions *sysfs_regions; | |
1847 | int i; | |
1848 | ||
1849 | for (i = 0; i < sysfs_schemes->nr; i++) { | |
1850 | sysfs_regions = sysfs_schemes->schemes_arr[i]->tried_regions; | |
1851 | if (sysfs_regions->upd_status == | |
1852 | DAMOS_TRIED_REGIONS_UPD_STARTED) | |
1853 | sysfs_regions->upd_status = | |
1854 | DAMOS_TRIED_REGIONS_UPD_FINISHED; | |
1855 | } | |
1856 | ||
1857 | return 0; | |
1858 | } | |
1859 | ||
f1d13cac | 1860 | /* Called from damon_sysfs_cmd_request_callback under damon_sysfs_lock */ |
772c15e5 | 1861 | int damon_sysfs_schemes_clear_regions( |
f1d13cac SP |
1862 | struct damon_sysfs_schemes *sysfs_schemes, |
1863 | struct damon_ctx *ctx) | |
1864 | { | |
1865 | struct damos *scheme; | |
1866 | int schemes_idx = 0; | |
1867 | ||
1868 | damon_for_each_scheme(scheme, ctx) { | |
1869 | struct damon_sysfs_scheme *sysfs_scheme; | |
1870 | ||
772c15e5 SP |
1871 | /* user could have removed the scheme sysfs dir */ |
1872 | if (schemes_idx >= sysfs_schemes->nr) | |
1873 | break; | |
1874 | ||
f1d13cac SP |
1875 | sysfs_scheme = sysfs_schemes->schemes_arr[schemes_idx++]; |
1876 | damon_sysfs_scheme_regions_rm_dirs( | |
1877 | sysfs_scheme->tried_regions); | |
b69f92a7 | 1878 | sysfs_scheme->tried_regions->total_bytes = 0; |
f1d13cac | 1879 | } |
772c15e5 SP |
1880 | return 0; |
1881 | } | |
f1d13cac | 1882 | |
4d4e41b6 SP |
1883 | static void damos_tried_regions_init_upd_status( |
1884 | struct damon_sysfs_schemes *sysfs_schemes) | |
1885 | { | |
1886 | int i; | |
1887 | ||
1888 | for (i = 0; i < sysfs_schemes->nr; i++) | |
1889 | sysfs_schemes->schemes_arr[i]->tried_regions->upd_status = | |
1890 | DAMOS_TRIED_REGIONS_UPD_IDLE; | |
1891 | } | |
1892 | ||
772c15e5 SP |
1893 | /* Called from damon_sysfs_cmd_request_callback under damon_sysfs_lock */ |
1894 | int damon_sysfs_schemes_update_regions_start( | |
1895 | struct damon_sysfs_schemes *sysfs_schemes, | |
6ad243b8 | 1896 | struct damon_ctx *ctx, bool total_bytes_only) |
772c15e5 SP |
1897 | { |
1898 | damon_sysfs_schemes_clear_regions(sysfs_schemes, ctx); | |
f1d13cac | 1899 | damon_sysfs_schemes_for_damos_callback = sysfs_schemes; |
4d4e41b6 | 1900 | damos_tried_regions_init_upd_status(sysfs_schemes); |
6ad243b8 | 1901 | damos_regions_upd_total_bytes_only = total_bytes_only; |
f1d13cac | 1902 | ctx->callback.before_damos_apply = damon_sysfs_before_damos_apply; |
4d4e41b6 | 1903 | ctx->callback.after_sampling = damon_sysfs_after_sampling; |
f1d13cac SP |
1904 | return 0; |
1905 | } | |
1906 | ||
76126332 SP |
1907 | bool damos_sysfs_regions_upd_done(void) |
1908 | { | |
1909 | struct damon_sysfs_schemes *sysfs_schemes = | |
1910 | damon_sysfs_schemes_for_damos_callback; | |
1911 | struct damon_sysfs_scheme_regions *sysfs_regions; | |
1912 | int i; | |
1913 | ||
1914 | for (i = 0; i < sysfs_schemes->nr; i++) { | |
1915 | sysfs_regions = sysfs_schemes->schemes_arr[i]->tried_regions; | |
1916 | if (sysfs_regions->upd_status != | |
1917 | DAMOS_TRIED_REGIONS_UPD_FINISHED) | |
1918 | return false; | |
1919 | } | |
1920 | return true; | |
1921 | } | |
1922 | ||
f1d13cac SP |
1923 | /* |
1924 | * Called from damon_sysfs_cmd_request_callback under damon_sysfs_lock. Caller | |
1925 | * should unlock damon_sysfs_lock which held before | |
1926 | * damon_sysfs_schemes_update_regions_start() | |
1927 | */ | |
1928 | int damon_sysfs_schemes_update_regions_stop(struct damon_ctx *ctx) | |
1929 | { | |
1930 | damon_sysfs_schemes_for_damos_callback = NULL; | |
1931 | ctx->callback.before_damos_apply = NULL; | |
4d4e41b6 | 1932 | ctx->callback.after_sampling = NULL; |
f1d13cac SP |
1933 | damon_sysfs_schemes_region_idx = 0; |
1934 | return 0; | |
1935 | } |