1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2020 Linaro Limited, All rights reserved. |
4 | * Author: Mike Leach <mike.leach@linaro.org> |
5 | */ |
6 | |
7 | #include <linux/configfs.h> |
8 | |
9 | #include "coresight-config.h" |
10 | #include "coresight-syscfg-configfs.h" |
11 | |
12 | /* create a default ci_type. */ |
13 | static inline struct config_item_type *cscfg_create_ci_type(void) |
14 | { |
15 | struct config_item_type *ci_type; |
16 | |
17 | ci_type = devm_kzalloc(dev: cscfg_device(), size: sizeof(*ci_type), GFP_KERNEL); |
18 | if (ci_type) |
19 | ci_type->ct_owner = THIS_MODULE; |
20 | |
21 | return ci_type; |
22 | } |
23 | |
24 | /* configurations sub-group */ |
25 | |
26 | /* attributes for the config view group */ |
27 | static ssize_t cscfg_cfg_description_show(struct config_item *item, char *page) |
28 | { |
29 | struct cscfg_fs_config *fs_config = container_of(to_config_group(item), |
30 | struct cscfg_fs_config, group); |
31 | |
32 | return scnprintf(buf: page, PAGE_SIZE, fmt: "%s" , fs_config->config_desc->description); |
33 | } |
34 | CONFIGFS_ATTR_RO(cscfg_cfg_, description); |
35 | |
36 | static ssize_t cscfg_cfg_feature_refs_show(struct config_item *item, char *page) |
37 | { |
38 | struct cscfg_fs_config *fs_config = container_of(to_config_group(item), |
39 | struct cscfg_fs_config, group); |
40 | const struct cscfg_config_desc *config_desc = fs_config->config_desc; |
41 | ssize_t ch_used = 0; |
42 | int i; |
43 | |
44 | for (i = 0; i < config_desc->nr_feat_refs; i++) |
45 | ch_used += scnprintf(buf: page + ch_used, PAGE_SIZE - ch_used, |
46 | fmt: "%s\n" , config_desc->feat_ref_names[i]); |
47 | return ch_used; |
48 | } |
49 | CONFIGFS_ATTR_RO(cscfg_cfg_, feature_refs); |
50 | |
51 | /* list preset values in order of features and params */ |
52 | static ssize_t cscfg_cfg_values_show(struct config_item *item, char *page) |
53 | { |
54 | const struct cscfg_feature_desc *feat_desc; |
55 | const struct cscfg_config_desc *config_desc; |
56 | struct cscfg_fs_preset *fs_preset; |
57 | int i, j, val_idx, preset_idx; |
58 | ssize_t used = 0; |
59 | |
60 | fs_preset = container_of(to_config_group(item), struct cscfg_fs_preset, group); |
61 | config_desc = fs_preset->config_desc; |
62 | |
63 | if (!config_desc->nr_presets) |
64 | return 0; |
65 | |
66 | preset_idx = fs_preset->preset_num - 1; |
67 | |
68 | /* start index on the correct array line */ |
69 | val_idx = config_desc->nr_total_params * preset_idx; |
70 | |
71 | /* |
72 | * A set of presets is the sum of all params in used features, |
73 | * in order of declaration of features and params in the features |
74 | */ |
75 | for (i = 0; i < config_desc->nr_feat_refs; i++) { |
76 | feat_desc = cscfg_get_named_feat_desc(name: config_desc->feat_ref_names[i]); |
77 | for (j = 0; j < feat_desc->nr_params; j++) { |
78 | used += scnprintf(buf: page + used, PAGE_SIZE - used, |
79 | fmt: "%s.%s = 0x%llx " , |
80 | feat_desc->name, |
81 | feat_desc->params_desc[j].name, |
82 | config_desc->presets[val_idx++]); |
83 | } |
84 | } |
85 | used += scnprintf(buf: page + used, PAGE_SIZE - used, fmt: "\n" ); |
86 | |
87 | return used; |
88 | } |
89 | CONFIGFS_ATTR_RO(cscfg_cfg_, values); |
90 | |
91 | static ssize_t cscfg_cfg_enable_show(struct config_item *item, char *page) |
92 | { |
93 | struct cscfg_fs_config *fs_config = container_of(to_config_group(item), |
94 | struct cscfg_fs_config, group); |
95 | |
96 | return scnprintf(buf: page, PAGE_SIZE, fmt: "%d\n" , fs_config->active); |
97 | } |
98 | |
99 | static ssize_t cscfg_cfg_enable_store(struct config_item *item, |
100 | const char *page, size_t count) |
101 | { |
102 | struct cscfg_fs_config *fs_config = container_of(to_config_group(item), |
103 | struct cscfg_fs_config, group); |
104 | int err; |
105 | bool val; |
106 | |
107 | err = kstrtobool(s: page, res: &val); |
108 | if (!err) |
109 | err = cscfg_config_sysfs_activate(cfg_desc: fs_config->config_desc, activate: val); |
110 | if (!err) { |
111 | fs_config->active = val; |
112 | if (val) |
113 | cscfg_config_sysfs_set_preset(preset: fs_config->preset); |
114 | } |
115 | return err ? err : count; |
116 | } |
117 | CONFIGFS_ATTR(cscfg_cfg_, enable); |
118 | |
119 | static ssize_t cscfg_cfg_preset_show(struct config_item *item, char *page) |
120 | { |
121 | struct cscfg_fs_config *fs_config = container_of(to_config_group(item), |
122 | struct cscfg_fs_config, group); |
123 | |
124 | return scnprintf(buf: page, PAGE_SIZE, fmt: "%d\n" , fs_config->preset); |
125 | } |
126 | |
127 | static ssize_t cscfg_cfg_preset_store(struct config_item *item, |
128 | const char *page, size_t count) |
129 | { |
130 | struct cscfg_fs_config *fs_config = container_of(to_config_group(item), |
131 | struct cscfg_fs_config, group); |
132 | int preset, err; |
133 | |
134 | err = kstrtoint(s: page, base: 0, res: &preset); |
135 | if (!err) { |
136 | /* |
137 | * presets start at 1, and go up to max (15), |
138 | * but the config may provide fewer. |
139 | */ |
140 | if ((preset < 1) || (preset > fs_config->config_desc->nr_presets)) |
141 | err = -EINVAL; |
142 | } |
143 | |
144 | if (!err) { |
145 | /* set new value */ |
146 | fs_config->preset = preset; |
147 | /* set on system if active */ |
148 | if (fs_config->active) |
149 | cscfg_config_sysfs_set_preset(preset: fs_config->preset); |
150 | } |
151 | return err ? err : count; |
152 | } |
153 | CONFIGFS_ATTR(cscfg_cfg_, preset); |
154 | |
155 | static struct configfs_attribute *cscfg_config_view_attrs[] = { |
156 | &cscfg_cfg_attr_description, |
157 | &cscfg_cfg_attr_feature_refs, |
158 | &cscfg_cfg_attr_enable, |
159 | &cscfg_cfg_attr_preset, |
160 | NULL, |
161 | }; |
162 | |
163 | static struct config_item_type cscfg_config_view_type = { |
164 | .ct_owner = THIS_MODULE, |
165 | .ct_attrs = cscfg_config_view_attrs, |
166 | }; |
167 | |
168 | static struct configfs_attribute *cscfg_config_preset_attrs[] = { |
169 | &cscfg_cfg_attr_values, |
170 | NULL, |
171 | }; |
172 | |
173 | static struct config_item_type cscfg_config_preset_type = { |
174 | .ct_owner = THIS_MODULE, |
175 | .ct_attrs = cscfg_config_preset_attrs, |
176 | }; |
177 | |
178 | static int cscfg_add_preset_groups(struct cscfg_fs_config *cfg_view) |
179 | { |
180 | int preset_num; |
181 | struct cscfg_fs_preset *cfg_fs_preset; |
182 | struct cscfg_config_desc *config_desc = cfg_view->config_desc; |
183 | char name[CONFIGFS_ITEM_NAME_LEN]; |
184 | |
185 | if (!config_desc->nr_presets) |
186 | return 0; |
187 | |
188 | for (preset_num = 1; preset_num <= config_desc->nr_presets; preset_num++) { |
189 | cfg_fs_preset = devm_kzalloc(dev: cscfg_device(), |
190 | size: sizeof(struct cscfg_fs_preset), GFP_KERNEL); |
191 | |
192 | if (!cfg_fs_preset) |
193 | return -ENOMEM; |
194 | |
195 | snprintf(buf: name, CONFIGFS_ITEM_NAME_LEN, fmt: "preset%d" , preset_num); |
196 | cfg_fs_preset->preset_num = preset_num; |
197 | cfg_fs_preset->config_desc = cfg_view->config_desc; |
198 | config_group_init_type_name(group: &cfg_fs_preset->group, name, |
199 | type: &cscfg_config_preset_type); |
200 | configfs_add_default_group(new_group: &cfg_fs_preset->group, group: &cfg_view->group); |
201 | } |
202 | return 0; |
203 | } |
204 | |
205 | static struct config_group *cscfg_create_config_group(struct cscfg_config_desc *config_desc) |
206 | { |
207 | struct cscfg_fs_config *cfg_view; |
208 | struct device *dev = cscfg_device(); |
209 | int err; |
210 | |
211 | if (!dev) |
212 | return ERR_PTR(error: -EINVAL); |
213 | |
214 | cfg_view = devm_kzalloc(dev, size: sizeof(struct cscfg_fs_config), GFP_KERNEL); |
215 | if (!cfg_view) |
216 | return ERR_PTR(error: -ENOMEM); |
217 | |
218 | cfg_view->config_desc = config_desc; |
219 | config_group_init_type_name(group: &cfg_view->group, name: config_desc->name, type: &cscfg_config_view_type); |
220 | |
221 | /* add in a preset<n> dir for each preset */ |
222 | err = cscfg_add_preset_groups(cfg_view); |
223 | if (err) |
224 | return ERR_PTR(error: err); |
225 | |
226 | return &cfg_view->group; |
227 | } |
228 | |
229 | /* attributes for features view */ |
230 | |
231 | static ssize_t cscfg_feat_description_show(struct config_item *item, char *page) |
232 | { |
233 | struct cscfg_fs_feature *fs_feat = container_of(to_config_group(item), |
234 | struct cscfg_fs_feature, group); |
235 | |
236 | return scnprintf(buf: page, PAGE_SIZE, fmt: "%s" , fs_feat->feat_desc->description); |
237 | } |
238 | CONFIGFS_ATTR_RO(cscfg_feat_, description); |
239 | |
240 | static ssize_t cscfg_feat_matches_show(struct config_item *item, char *page) |
241 | { |
242 | struct cscfg_fs_feature *fs_feat = container_of(to_config_group(item), |
243 | struct cscfg_fs_feature, group); |
244 | u32 match_flags = fs_feat->feat_desc->match_flags; |
245 | int used = 0; |
246 | |
247 | if (match_flags & CS_CFG_MATCH_CLASS_SRC_ALL) |
248 | used = scnprintf(buf: page, PAGE_SIZE, fmt: "SRC_ALL " ); |
249 | |
250 | if (match_flags & CS_CFG_MATCH_CLASS_SRC_ETM4) |
251 | used += scnprintf(buf: page + used, PAGE_SIZE - used, fmt: "SRC_ETMV4 " ); |
252 | |
253 | used += scnprintf(buf: page + used, PAGE_SIZE - used, fmt: "\n" ); |
254 | return used; |
255 | } |
256 | CONFIGFS_ATTR_RO(cscfg_feat_, matches); |
257 | |
258 | static ssize_t cscfg_feat_nr_params_show(struct config_item *item, char *page) |
259 | { |
260 | struct cscfg_fs_feature *fs_feat = container_of(to_config_group(item), |
261 | struct cscfg_fs_feature, group); |
262 | |
263 | return scnprintf(buf: page, PAGE_SIZE, fmt: "%d\n" , fs_feat->feat_desc->nr_params); |
264 | } |
265 | CONFIGFS_ATTR_RO(cscfg_feat_, nr_params); |
266 | |
267 | /* base feature desc attrib structures */ |
268 | static struct configfs_attribute *cscfg_feature_view_attrs[] = { |
269 | &cscfg_feat_attr_description, |
270 | &cscfg_feat_attr_matches, |
271 | &cscfg_feat_attr_nr_params, |
272 | NULL, |
273 | }; |
274 | |
275 | static struct config_item_type cscfg_feature_view_type = { |
276 | .ct_owner = THIS_MODULE, |
277 | .ct_attrs = cscfg_feature_view_attrs, |
278 | }; |
279 | |
280 | static ssize_t cscfg_param_value_show(struct config_item *item, char *page) |
281 | { |
282 | struct cscfg_fs_param *param_item = container_of(to_config_group(item), |
283 | struct cscfg_fs_param, group); |
284 | u64 value = param_item->feat_desc->params_desc[param_item->param_idx].value; |
285 | |
286 | return scnprintf(buf: page, PAGE_SIZE, fmt: "0x%llx\n" , value); |
287 | } |
288 | |
289 | static ssize_t cscfg_param_value_store(struct config_item *item, |
290 | const char *page, size_t size) |
291 | { |
292 | struct cscfg_fs_param *param_item = container_of(to_config_group(item), |
293 | struct cscfg_fs_param, group); |
294 | struct cscfg_feature_desc *feat_desc = param_item->feat_desc; |
295 | int param_idx = param_item->param_idx; |
296 | u64 value; |
297 | int err; |
298 | |
299 | err = kstrtoull(s: page, base: 0, res: &value); |
300 | if (!err) |
301 | err = cscfg_update_feat_param_val(feat_desc, param_idx, value); |
302 | |
303 | return err ? err : size; |
304 | } |
305 | CONFIGFS_ATTR(cscfg_param_, value); |
306 | |
307 | static struct configfs_attribute *cscfg_param_view_attrs[] = { |
308 | &cscfg_param_attr_value, |
309 | NULL, |
310 | }; |
311 | |
312 | static struct config_item_type cscfg_param_view_type = { |
313 | .ct_owner = THIS_MODULE, |
314 | .ct_attrs = cscfg_param_view_attrs, |
315 | }; |
316 | |
317 | /* |
318 | * configfs has far less functionality provided to add attributes dynamically than sysfs, |
319 | * and the show and store fns pass the enclosing config_item so the actual attribute cannot |
320 | * be determined. Therefore we add each item as a group directory, with a value attribute. |
321 | */ |
322 | static int cscfg_create_params_group_items(struct cscfg_feature_desc *feat_desc, |
323 | struct config_group *params_group) |
324 | { |
325 | struct device *dev = cscfg_device(); |
326 | struct cscfg_fs_param *param_item; |
327 | int i; |
328 | |
329 | /* parameter items - as groups with default_value attribute */ |
330 | for (i = 0; i < feat_desc->nr_params; i++) { |
331 | param_item = devm_kzalloc(dev, size: sizeof(struct cscfg_fs_param), GFP_KERNEL); |
332 | if (!param_item) |
333 | return -ENOMEM; |
334 | param_item->feat_desc = feat_desc; |
335 | param_item->param_idx = i; |
336 | config_group_init_type_name(group: ¶m_item->group, |
337 | name: feat_desc->params_desc[i].name, |
338 | type: &cscfg_param_view_type); |
339 | configfs_add_default_group(new_group: ¶m_item->group, group: params_group); |
340 | } |
341 | return 0; |
342 | } |
343 | |
344 | static struct config_group *cscfg_create_feature_group(struct cscfg_feature_desc *feat_desc) |
345 | { |
346 | struct cscfg_fs_feature *feat_view; |
347 | struct config_item_type *params_group_type; |
348 | struct config_group *params_group = NULL; |
349 | struct device *dev = cscfg_device(); |
350 | int item_err; |
351 | |
352 | if (!dev) |
353 | return ERR_PTR(error: -EINVAL); |
354 | |
355 | feat_view = devm_kzalloc(dev, size: sizeof(struct cscfg_fs_feature), GFP_KERNEL); |
356 | if (!feat_view) |
357 | return ERR_PTR(error: -ENOMEM); |
358 | |
359 | if (feat_desc->nr_params) { |
360 | params_group = devm_kzalloc(dev, size: sizeof(struct config_group), GFP_KERNEL); |
361 | if (!params_group) |
362 | return ERR_PTR(error: -ENOMEM); |
363 | |
364 | params_group_type = cscfg_create_ci_type(); |
365 | if (!params_group_type) |
366 | return ERR_PTR(error: -ENOMEM); |
367 | } |
368 | |
369 | feat_view->feat_desc = feat_desc; |
370 | config_group_init_type_name(group: &feat_view->group, |
371 | name: feat_desc->name, |
372 | type: &cscfg_feature_view_type); |
373 | if (params_group) { |
374 | config_group_init_type_name(group: params_group, name: "params" , type: params_group_type); |
375 | configfs_add_default_group(new_group: params_group, group: &feat_view->group); |
376 | item_err = cscfg_create_params_group_items(feat_desc, params_group); |
377 | if (item_err) |
378 | return ERR_PTR(error: item_err); |
379 | } |
380 | return &feat_view->group; |
381 | } |
382 | |
383 | static struct config_item_type cscfg_configs_type = { |
384 | .ct_owner = THIS_MODULE, |
385 | }; |
386 | |
387 | static struct config_group cscfg_configs_grp = { |
388 | .cg_item = { |
389 | .ci_namebuf = "configurations" , |
390 | .ci_type = &cscfg_configs_type, |
391 | }, |
392 | }; |
393 | |
394 | /* add configuration to configurations group */ |
395 | int cscfg_configfs_add_config(struct cscfg_config_desc *config_desc) |
396 | { |
397 | struct config_group *new_group; |
398 | int err; |
399 | |
400 | new_group = cscfg_create_config_group(config_desc); |
401 | if (IS_ERR(ptr: new_group)) |
402 | return PTR_ERR(ptr: new_group); |
403 | err = configfs_register_group(parent_group: &cscfg_configs_grp, group: new_group); |
404 | if (!err) |
405 | config_desc->fs_group = new_group; |
406 | return err; |
407 | } |
408 | |
409 | void cscfg_configfs_del_config(struct cscfg_config_desc *config_desc) |
410 | { |
411 | if (config_desc->fs_group) { |
412 | configfs_unregister_group(group: config_desc->fs_group); |
413 | config_desc->fs_group = NULL; |
414 | } |
415 | } |
416 | |
417 | static struct config_item_type cscfg_features_type = { |
418 | .ct_owner = THIS_MODULE, |
419 | }; |
420 | |
421 | static struct config_group cscfg_features_grp = { |
422 | .cg_item = { |
423 | .ci_namebuf = "features" , |
424 | .ci_type = &cscfg_features_type, |
425 | }, |
426 | }; |
427 | |
428 | /* add feature to features group */ |
429 | int cscfg_configfs_add_feature(struct cscfg_feature_desc *feat_desc) |
430 | { |
431 | struct config_group *new_group; |
432 | int err; |
433 | |
434 | new_group = cscfg_create_feature_group(feat_desc); |
435 | if (IS_ERR(ptr: new_group)) |
436 | return PTR_ERR(ptr: new_group); |
437 | err = configfs_register_group(parent_group: &cscfg_features_grp, group: new_group); |
438 | if (!err) |
439 | feat_desc->fs_group = new_group; |
440 | return err; |
441 | } |
442 | |
443 | void cscfg_configfs_del_feature(struct cscfg_feature_desc *feat_desc) |
444 | { |
445 | if (feat_desc->fs_group) { |
446 | configfs_unregister_group(group: feat_desc->fs_group); |
447 | feat_desc->fs_group = NULL; |
448 | } |
449 | } |
450 | |
451 | int cscfg_configfs_init(struct cscfg_manager *cscfg_mgr) |
452 | { |
453 | struct configfs_subsystem *subsys; |
454 | struct config_item_type *ci_type; |
455 | |
456 | if (!cscfg_mgr) |
457 | return -EINVAL; |
458 | |
459 | ci_type = cscfg_create_ci_type(); |
460 | if (!ci_type) |
461 | return -ENOMEM; |
462 | |
463 | subsys = &cscfg_mgr->cfgfs_subsys; |
464 | config_item_set_name(&subsys->su_group.cg_item, CSCFG_FS_SUBSYS_NAME); |
465 | subsys->su_group.cg_item.ci_type = ci_type; |
466 | |
467 | config_group_init(group: &subsys->su_group); |
468 | mutex_init(&subsys->su_mutex); |
469 | |
470 | /* Add default groups to subsystem */ |
471 | config_group_init(group: &cscfg_configs_grp); |
472 | configfs_add_default_group(new_group: &cscfg_configs_grp, group: &subsys->su_group); |
473 | |
474 | config_group_init(group: &cscfg_features_grp); |
475 | configfs_add_default_group(new_group: &cscfg_features_grp, group: &subsys->su_group); |
476 | |
477 | return configfs_register_subsystem(subsys); |
478 | } |
479 | |
480 | void cscfg_configfs_release(struct cscfg_manager *cscfg_mgr) |
481 | { |
482 | configfs_unregister_subsystem(subsys: &cscfg_mgr->cfgfs_subsys); |
483 | } |
484 | |