]>
Commit | Line | Data |
---|---|---|
9ea393d8 | 1 | // SPDX-License-Identifier: GPL-2.0 |
7bd1d409 AS |
2 | /* |
3 | * System Trace Module (STM) master/channel allocation policy management | |
4 | * Copyright (c) 2014, Intel Corporation. | |
5 | * | |
7bd1d409 AS |
6 | * A master/channel allocation policy allows mapping string identifiers to |
7 | * master and channel ranges, where allocation can be done. | |
8 | */ | |
9 | ||
10 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
11 | ||
12 | #include <linux/types.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/device.h> | |
15 | #include <linux/configfs.h> | |
16 | #include <linux/slab.h> | |
17 | #include <linux/stm.h> | |
18 | #include "stm.h" | |
19 | ||
20 | /* | |
21 | * STP Master/Channel allocation policy configfs layout. | |
22 | */ | |
23 | ||
24 | struct stp_policy { | |
25 | struct config_group group; | |
26 | struct stm_device *stm; | |
27 | }; | |
28 | ||
29 | struct stp_policy_node { | |
30 | struct config_group group; | |
31 | struct stp_policy *policy; | |
32 | unsigned int first_master; | |
33 | unsigned int last_master; | |
34 | unsigned int first_channel; | |
35 | unsigned int last_channel; | |
c7fd62bc AS |
36 | /* this is the one that's exposed to the attributes */ |
37 | unsigned char priv[0]; | |
7bd1d409 AS |
38 | }; |
39 | ||
c7fd62bc AS |
40 | void *stp_policy_node_priv(struct stp_policy_node *pn) |
41 | { | |
42 | if (!pn) | |
43 | return NULL; | |
44 | ||
45 | return pn->priv; | |
46 | } | |
47 | ||
7bd1d409 AS |
48 | static struct configfs_subsystem stp_policy_subsys; |
49 | ||
50 | void stp_policy_node_get_ranges(struct stp_policy_node *policy_node, | |
51 | unsigned int *mstart, unsigned int *mend, | |
52 | unsigned int *cstart, unsigned int *cend) | |
53 | { | |
54 | *mstart = policy_node->first_master; | |
55 | *mend = policy_node->last_master; | |
56 | *cstart = policy_node->first_channel; | |
57 | *cend = policy_node->last_channel; | |
58 | } | |
59 | ||
60 | static inline char *stp_policy_node_name(struct stp_policy_node *policy_node) | |
61 | { | |
62 | return policy_node->group.cg_item.ci_name ? : "<none>"; | |
63 | } | |
64 | ||
65 | static inline struct stp_policy *to_stp_policy(struct config_item *item) | |
66 | { | |
67 | return item ? | |
68 | container_of(to_config_group(item), struct stp_policy, group) : | |
69 | NULL; | |
70 | } | |
71 | ||
72 | static inline struct stp_policy_node * | |
73 | to_stp_policy_node(struct config_item *item) | |
74 | { | |
75 | return item ? | |
76 | container_of(to_config_group(item), struct stp_policy_node, | |
77 | group) : | |
78 | NULL; | |
79 | } | |
80 | ||
c7fd62bc AS |
81 | void *to_pdrv_policy_node(struct config_item *item) |
82 | { | |
83 | struct stp_policy_node *node = to_stp_policy_node(item); | |
84 | ||
85 | return stp_policy_node_priv(node); | |
86 | } | |
87 | EXPORT_SYMBOL_GPL(to_pdrv_policy_node); | |
88 | ||
9aa3d651 LT |
89 | static ssize_t |
90 | stp_policy_node_masters_show(struct config_item *item, char *page) | |
7bd1d409 | 91 | { |
9aa3d651 | 92 | struct stp_policy_node *policy_node = to_stp_policy_node(item); |
7bd1d409 AS |
93 | ssize_t count; |
94 | ||
95 | count = sprintf(page, "%u %u\n", policy_node->first_master, | |
96 | policy_node->last_master); | |
97 | ||
98 | return count; | |
99 | } | |
100 | ||
101 | static ssize_t | |
9aa3d651 LT |
102 | stp_policy_node_masters_store(struct config_item *item, const char *page, |
103 | size_t count) | |
7bd1d409 | 104 | { |
9aa3d651 | 105 | struct stp_policy_node *policy_node = to_stp_policy_node(item); |
7bd1d409 AS |
106 | unsigned int first, last; |
107 | struct stm_device *stm; | |
108 | char *p = (char *)page; | |
109 | ssize_t ret = -ENODEV; | |
110 | ||
111 | if (sscanf(p, "%u %u", &first, &last) != 2) | |
112 | return -EINVAL; | |
113 | ||
114 | mutex_lock(&stp_policy_subsys.su_mutex); | |
115 | stm = policy_node->policy->stm; | |
116 | if (!stm) | |
117 | goto unlock; | |
118 | ||
119 | /* must be within [sw_start..sw_end], which is an inclusive range */ | |
f57af6df | 120 | if (first > last || first < stm->data->sw_start || |
7bd1d409 AS |
121 | last > stm->data->sw_end) { |
122 | ret = -ERANGE; | |
123 | goto unlock; | |
124 | } | |
125 | ||
126 | ret = count; | |
127 | policy_node->first_master = first; | |
128 | policy_node->last_master = last; | |
129 | ||
130 | unlock: | |
131 | mutex_unlock(&stp_policy_subsys.su_mutex); | |
132 | ||
133 | return ret; | |
134 | } | |
135 | ||
136 | static ssize_t | |
9aa3d651 | 137 | stp_policy_node_channels_show(struct config_item *item, char *page) |
7bd1d409 | 138 | { |
9aa3d651 | 139 | struct stp_policy_node *policy_node = to_stp_policy_node(item); |
7bd1d409 AS |
140 | ssize_t count; |
141 | ||
142 | count = sprintf(page, "%u %u\n", policy_node->first_channel, | |
143 | policy_node->last_channel); | |
144 | ||
145 | return count; | |
146 | } | |
147 | ||
148 | static ssize_t | |
9aa3d651 LT |
149 | stp_policy_node_channels_store(struct config_item *item, const char *page, |
150 | size_t count) | |
7bd1d409 | 151 | { |
9aa3d651 | 152 | struct stp_policy_node *policy_node = to_stp_policy_node(item); |
7bd1d409 AS |
153 | unsigned int first, last; |
154 | struct stm_device *stm; | |
155 | char *p = (char *)page; | |
156 | ssize_t ret = -ENODEV; | |
157 | ||
158 | if (sscanf(p, "%u %u", &first, &last) != 2) | |
159 | return -EINVAL; | |
160 | ||
161 | mutex_lock(&stp_policy_subsys.su_mutex); | |
162 | stm = policy_node->policy->stm; | |
163 | if (!stm) | |
164 | goto unlock; | |
165 | ||
166 | if (first > INT_MAX || last > INT_MAX || first > last || | |
167 | last >= stm->data->sw_nchannels) { | |
168 | ret = -ERANGE; | |
169 | goto unlock; | |
170 | } | |
171 | ||
172 | ret = count; | |
173 | policy_node->first_channel = first; | |
174 | policy_node->last_channel = last; | |
175 | ||
176 | unlock: | |
177 | mutex_unlock(&stp_policy_subsys.su_mutex); | |
178 | ||
179 | return ret; | |
180 | } | |
181 | ||
182 | static void stp_policy_node_release(struct config_item *item) | |
183 | { | |
c7fd62bc AS |
184 | struct stp_policy_node *node = to_stp_policy_node(item); |
185 | ||
186 | kfree(node); | |
7bd1d409 AS |
187 | } |
188 | ||
7bd1d409 AS |
189 | static struct configfs_item_operations stp_policy_node_item_ops = { |
190 | .release = stp_policy_node_release, | |
7bd1d409 AS |
191 | }; |
192 | ||
9aa3d651 LT |
193 | CONFIGFS_ATTR(stp_policy_node_, masters); |
194 | CONFIGFS_ATTR(stp_policy_node_, channels); | |
7bd1d409 AS |
195 | |
196 | static struct configfs_attribute *stp_policy_node_attrs[] = { | |
9aa3d651 LT |
197 | &stp_policy_node_attr_masters, |
198 | &stp_policy_node_attr_channels, | |
7bd1d409 AS |
199 | NULL, |
200 | }; | |
201 | ||
085006e8 BG |
202 | static const struct config_item_type stp_policy_type; |
203 | static const struct config_item_type stp_policy_node_type; | |
7bd1d409 | 204 | |
c7fd62bc AS |
205 | const struct config_item_type * |
206 | get_policy_node_type(struct configfs_attribute **attrs) | |
207 | { | |
208 | struct config_item_type *type; | |
209 | struct configfs_attribute **merged; | |
210 | ||
211 | type = kmemdup(&stp_policy_node_type, sizeof(stp_policy_node_type), | |
212 | GFP_KERNEL); | |
213 | if (!type) | |
214 | return NULL; | |
215 | ||
a23bbec2 | 216 | merged = memcat_p(stp_policy_node_attrs, attrs); |
c7fd62bc AS |
217 | if (!merged) { |
218 | kfree(type); | |
219 | return NULL; | |
220 | } | |
221 | ||
222 | type->ct_attrs = merged; | |
223 | ||
224 | return type; | |
225 | } | |
226 | ||
7bd1d409 AS |
227 | static struct config_group * |
228 | stp_policy_node_make(struct config_group *group, const char *name) | |
229 | { | |
c7fd62bc | 230 | const struct config_item_type *type = &stp_policy_node_type; |
7bd1d409 | 231 | struct stp_policy_node *policy_node, *parent_node; |
c7fd62bc | 232 | const struct stm_protocol_driver *pdrv; |
7bd1d409 AS |
233 | struct stp_policy *policy; |
234 | ||
235 | if (group->cg_item.ci_type == &stp_policy_type) { | |
236 | policy = container_of(group, struct stp_policy, group); | |
237 | } else { | |
238 | parent_node = container_of(group, struct stp_policy_node, | |
239 | group); | |
240 | policy = parent_node->policy; | |
241 | } | |
242 | ||
243 | if (!policy->stm) | |
244 | return ERR_PTR(-ENODEV); | |
245 | ||
c7fd62bc AS |
246 | pdrv = policy->stm->pdrv; |
247 | policy_node = | |
248 | kzalloc(offsetof(struct stp_policy_node, priv[pdrv->priv_sz]), | |
249 | GFP_KERNEL); | |
7bd1d409 AS |
250 | if (!policy_node) |
251 | return ERR_PTR(-ENOMEM); | |
252 | ||
c7fd62bc AS |
253 | if (pdrv->policy_node_init) |
254 | pdrv->policy_node_init((void *)policy_node->priv); | |
255 | ||
256 | if (policy->stm->pdrv_node_type) | |
257 | type = policy->stm->pdrv_node_type; | |
258 | ||
259 | config_group_init_type_name(&policy_node->group, name, type); | |
7bd1d409 AS |
260 | |
261 | policy_node->policy = policy; | |
262 | ||
263 | /* default values for the attributes */ | |
264 | policy_node->first_master = policy->stm->data->sw_start; | |
265 | policy_node->last_master = policy->stm->data->sw_end; | |
266 | policy_node->first_channel = 0; | |
267 | policy_node->last_channel = policy->stm->data->sw_nchannels - 1; | |
268 | ||
269 | return &policy_node->group; | |
270 | } | |
271 | ||
272 | static void | |
273 | stp_policy_node_drop(struct config_group *group, struct config_item *item) | |
274 | { | |
275 | config_item_put(item); | |
276 | } | |
277 | ||
278 | static struct configfs_group_operations stp_policy_node_group_ops = { | |
279 | .make_group = stp_policy_node_make, | |
280 | .drop_item = stp_policy_node_drop, | |
281 | }; | |
282 | ||
085006e8 | 283 | static const struct config_item_type stp_policy_node_type = { |
7bd1d409 AS |
284 | .ct_item_ops = &stp_policy_node_item_ops, |
285 | .ct_group_ops = &stp_policy_node_group_ops, | |
286 | .ct_attrs = stp_policy_node_attrs, | |
287 | .ct_owner = THIS_MODULE, | |
288 | }; | |
289 | ||
290 | /* | |
291 | * Root group: policies. | |
292 | */ | |
9aa3d651 LT |
293 | static ssize_t stp_policy_device_show(struct config_item *item, |
294 | char *page) | |
7bd1d409 AS |
295 | { |
296 | struct stp_policy *policy = to_stp_policy(item); | |
297 | ssize_t count; | |
298 | ||
299 | count = sprintf(page, "%s\n", | |
300 | (policy && policy->stm) ? | |
301 | policy->stm->data->name : | |
302 | "<none>"); | |
303 | ||
304 | return count; | |
305 | } | |
306 | ||
9aa3d651 LT |
307 | CONFIGFS_ATTR_RO(stp_policy_, device); |
308 | ||
c7fd62bc AS |
309 | static ssize_t stp_policy_protocol_show(struct config_item *item, |
310 | char *page) | |
311 | { | |
312 | struct stp_policy *policy = to_stp_policy(item); | |
313 | ssize_t count; | |
314 | ||
315 | count = sprintf(page, "%s\n", | |
316 | (policy && policy->stm) ? | |
317 | policy->stm->pdrv->name : | |
318 | "<none>"); | |
319 | ||
320 | return count; | |
321 | } | |
322 | ||
323 | CONFIGFS_ATTR_RO(stp_policy_, protocol); | |
324 | ||
9aa3d651 LT |
325 | static struct configfs_attribute *stp_policy_attrs[] = { |
326 | &stp_policy_attr_device, | |
c7fd62bc | 327 | &stp_policy_attr_protocol, |
9aa3d651 LT |
328 | NULL, |
329 | }; | |
330 | ||
7bd1d409 AS |
331 | void stp_policy_unbind(struct stp_policy *policy) |
332 | { | |
333 | struct stm_device *stm = policy->stm; | |
334 | ||
4c127fd1 AS |
335 | /* |
336 | * stp_policy_release() will not call here if the policy is already | |
337 | * unbound; other users should not either, as no link exists between | |
338 | * this policy and anything else in that case | |
339 | */ | |
7bd1d409 AS |
340 | if (WARN_ON_ONCE(!policy->stm)) |
341 | return; | |
342 | ||
4c127fd1 | 343 | lockdep_assert_held(&stm->policy_mutex); |
7bd1d409 | 344 | |
4c127fd1 | 345 | stm->policy = NULL; |
7bd1d409 AS |
346 | policy->stm = NULL; |
347 | ||
c7fd62bc | 348 | stm_put_protocol(stm->pdrv); |
7bd1d409 AS |
349 | stm_put_device(stm); |
350 | } | |
351 | ||
352 | static void stp_policy_release(struct config_item *item) | |
353 | { | |
354 | struct stp_policy *policy = to_stp_policy(item); | |
4c127fd1 | 355 | struct stm_device *stm = policy->stm; |
7bd1d409 | 356 | |
4c127fd1 AS |
357 | /* a policy *can* be unbound and still exist in configfs tree */ |
358 | if (!stm) | |
359 | return; | |
360 | ||
361 | mutex_lock(&stm->policy_mutex); | |
7bd1d409 | 362 | stp_policy_unbind(policy); |
4c127fd1 AS |
363 | mutex_unlock(&stm->policy_mutex); |
364 | ||
7bd1d409 AS |
365 | kfree(policy); |
366 | } | |
367 | ||
368 | static struct configfs_item_operations stp_policy_item_ops = { | |
369 | .release = stp_policy_release, | |
7bd1d409 AS |
370 | }; |
371 | ||
372 | static struct configfs_group_operations stp_policy_group_ops = { | |
373 | .make_group = stp_policy_node_make, | |
374 | }; | |
375 | ||
085006e8 | 376 | static const struct config_item_type stp_policy_type = { |
7bd1d409 AS |
377 | .ct_item_ops = &stp_policy_item_ops, |
378 | .ct_group_ops = &stp_policy_group_ops, | |
379 | .ct_attrs = stp_policy_attrs, | |
380 | .ct_owner = THIS_MODULE, | |
381 | }; | |
382 | ||
383 | static struct config_group * | |
25e3c006 | 384 | stp_policy_make(struct config_group *group, const char *name) |
7bd1d409 | 385 | { |
c7fd62bc AS |
386 | const struct config_item_type *pdrv_node_type; |
387 | const struct stm_protocol_driver *pdrv; | |
388 | char *devname, *proto, *p; | |
7bd1d409 AS |
389 | struct config_group *ret; |
390 | struct stm_device *stm; | |
c7fd62bc | 391 | int err; |
7bd1d409 AS |
392 | |
393 | devname = kasprintf(GFP_KERNEL, "%s", name); | |
394 | if (!devname) | |
395 | return ERR_PTR(-ENOMEM); | |
396 | ||
397 | /* | |
398 | * node must look like <device_name>.<policy_name>, where | |
59be422e AS |
399 | * <device_name> is the name of an existing stm device; may |
400 | * contain dots; | |
401 | * <policy_name> is an arbitrary string; may not contain dots | |
c7fd62bc | 402 | * <device_name>:<protocol_name>.<policy_name> |
7bd1d409 | 403 | */ |
59be422e | 404 | p = strrchr(devname, '.'); |
7bd1d409 AS |
405 | if (!p) { |
406 | kfree(devname); | |
407 | return ERR_PTR(-EINVAL); | |
408 | } | |
409 | ||
fb080190 | 410 | *p = '\0'; |
7bd1d409 | 411 | |
c7fd62bc AS |
412 | /* |
413 | * look for ":<protocol_name>": | |
414 | * + no protocol suffix: fall back to whatever is available; | |
415 | * + unknown protocol: fail the whole thing | |
416 | */ | |
417 | proto = strrchr(devname, ':'); | |
418 | if (proto) | |
419 | *proto++ = '\0'; | |
420 | ||
7bd1d409 | 421 | stm = stm_find_device(devname); |
c7fd62bc AS |
422 | if (!stm) { |
423 | kfree(devname); | |
424 | return ERR_PTR(-ENODEV); | |
425 | } | |
426 | ||
427 | err = stm_lookup_protocol(proto, &pdrv, &pdrv_node_type); | |
7bd1d409 AS |
428 | kfree(devname); |
429 | ||
24c7bcb6 | 430 | if (err) { |
c7fd62bc | 431 | stm_put_device(stm); |
7bd1d409 | 432 | return ERR_PTR(-ENODEV); |
c7fd62bc | 433 | } |
7bd1d409 AS |
434 | |
435 | mutex_lock(&stm->policy_mutex); | |
436 | if (stm->policy) { | |
437 | ret = ERR_PTR(-EBUSY); | |
438 | goto unlock_policy; | |
439 | } | |
440 | ||
441 | stm->policy = kzalloc(sizeof(*stm->policy), GFP_KERNEL); | |
442 | if (!stm->policy) { | |
c18614a1 AS |
443 | ret = ERR_PTR(-ENOMEM); |
444 | goto unlock_policy; | |
7bd1d409 AS |
445 | } |
446 | ||
447 | config_group_init_type_name(&stm->policy->group, name, | |
448 | &stp_policy_type); | |
7bd1d409 | 449 | |
c7fd62bc AS |
450 | stm->pdrv = pdrv; |
451 | stm->pdrv_node_type = pdrv_node_type; | |
452 | stm->policy->stm = stm; | |
7bd1d409 AS |
453 | ret = &stm->policy->group; |
454 | ||
455 | unlock_policy: | |
456 | mutex_unlock(&stm->policy_mutex); | |
457 | ||
c7fd62bc | 458 | if (IS_ERR(ret)) { |
c18614a1 AS |
459 | /* |
460 | * pdrv and stm->pdrv at this point can be quite different, | |
461 | * and only one of them needs to be 'put' | |
462 | */ | |
463 | stm_put_protocol(pdrv); | |
7bd1d409 | 464 | stm_put_device(stm); |
c7fd62bc | 465 | } |
7bd1d409 AS |
466 | |
467 | return ret; | |
468 | } | |
469 | ||
25e3c006 AS |
470 | static struct configfs_group_operations stp_policy_root_group_ops = { |
471 | .make_group = stp_policy_make, | |
7bd1d409 AS |
472 | }; |
473 | ||
25e3c006 AS |
474 | static const struct config_item_type stp_policy_root_type = { |
475 | .ct_group_ops = &stp_policy_root_group_ops, | |
7bd1d409 AS |
476 | .ct_owner = THIS_MODULE, |
477 | }; | |
478 | ||
479 | static struct configfs_subsystem stp_policy_subsys = { | |
480 | .su_group = { | |
481 | .cg_item = { | |
482 | .ci_namebuf = "stp-policy", | |
25e3c006 | 483 | .ci_type = &stp_policy_root_type, |
7bd1d409 AS |
484 | }, |
485 | }, | |
486 | }; | |
487 | ||
488 | /* | |
489 | * Lock the policy mutex from the outside | |
490 | */ | |
491 | static struct stp_policy_node * | |
492 | __stp_policy_node_lookup(struct stp_policy *policy, char *s) | |
493 | { | |
cb6102bd | 494 | struct stp_policy_node *policy_node, *ret = NULL; |
7bd1d409 AS |
495 | struct list_head *head = &policy->group.cg_children; |
496 | struct config_item *item; | |
497 | char *start, *end = s; | |
498 | ||
499 | if (list_empty(head)) | |
500 | return NULL; | |
501 | ||
7bd1d409 AS |
502 | next: |
503 | for (;;) { | |
504 | start = strsep(&end, "/"); | |
505 | if (!start) | |
506 | break; | |
507 | ||
508 | if (!*start) | |
509 | continue; | |
510 | ||
511 | list_for_each_entry(item, head, ci_entry) { | |
512 | policy_node = to_stp_policy_node(item); | |
513 | ||
514 | if (!strcmp(start, | |
515 | policy_node->group.cg_item.ci_name)) { | |
516 | ret = policy_node; | |
517 | ||
518 | if (!end) | |
519 | goto out; | |
520 | ||
521 | head = &policy_node->group.cg_children; | |
522 | goto next; | |
523 | } | |
524 | } | |
525 | break; | |
526 | } | |
527 | ||
528 | out: | |
529 | return ret; | |
530 | } | |
531 | ||
532 | ||
533 | struct stp_policy_node * | |
534 | stp_policy_node_lookup(struct stm_device *stm, char *s) | |
535 | { | |
536 | struct stp_policy_node *policy_node = NULL; | |
537 | ||
538 | mutex_lock(&stp_policy_subsys.su_mutex); | |
539 | ||
540 | mutex_lock(&stm->policy_mutex); | |
541 | if (stm->policy) | |
542 | policy_node = __stp_policy_node_lookup(stm->policy, s); | |
543 | mutex_unlock(&stm->policy_mutex); | |
544 | ||
545 | if (policy_node) | |
546 | config_item_get(&policy_node->group.cg_item); | |
cb6102bd AS |
547 | else |
548 | mutex_unlock(&stp_policy_subsys.su_mutex); | |
7bd1d409 AS |
549 | |
550 | return policy_node; | |
551 | } | |
552 | ||
553 | void stp_policy_node_put(struct stp_policy_node *policy_node) | |
554 | { | |
cb6102bd AS |
555 | lockdep_assert_held(&stp_policy_subsys.su_mutex); |
556 | ||
557 | mutex_unlock(&stp_policy_subsys.su_mutex); | |
7bd1d409 AS |
558 | config_item_put(&policy_node->group.cg_item); |
559 | } | |
560 | ||
561 | int __init stp_configfs_init(void) | |
562 | { | |
7bd1d409 AS |
563 | config_group_init(&stp_policy_subsys.su_group); |
564 | mutex_init(&stp_policy_subsys.su_mutex); | |
e967b8bd | 565 | return configfs_register_subsystem(&stp_policy_subsys); |
7bd1d409 AS |
566 | } |
567 | ||
568 | void __exit stp_configfs_exit(void) | |
569 | { | |
570 | configfs_unregister_subsystem(&stp_policy_subsys); | |
571 | } |