1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2022-23 IBM Corp. |
4 | */ |
5 | |
6 | #define pr_fmt(fmt) "vas: " fmt |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/miscdevice.h> |
11 | #include <linux/kobject.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/mm.h> |
14 | |
15 | #include "vas.h" |
16 | |
17 | #ifdef CONFIG_SYSFS |
18 | static struct kobject *pseries_vas_kobj; |
19 | static struct kobject *gzip_caps_kobj; |
20 | |
21 | struct vas_caps_entry { |
22 | struct kobject kobj; |
23 | struct vas_cop_feat_caps *caps; |
24 | }; |
25 | |
26 | #define to_caps_entry(entry) container_of(entry, struct vas_caps_entry, kobj) |
27 | |
28 | /* |
29 | * This function is used to get the notification from the drmgr when |
30 | * QoS credits are changed. |
31 | */ |
32 | static ssize_t update_total_credits_store(struct vas_cop_feat_caps *caps, |
33 | const char *buf, size_t count) |
34 | { |
35 | int err; |
36 | u16 creds; |
37 | |
38 | err = kstrtou16(s: buf, base: 0, res: &creds); |
39 | /* |
40 | * The user space interface from the management console |
41 | * notifies OS with the new QoS credits and then the |
42 | * hypervisor. So OS has to use this new credits value |
43 | * and reconfigure VAS windows (close or reopen depends |
44 | * on the credits available) instead of depending on VAS |
45 | * QoS capabilities from the hypervisor. |
46 | */ |
47 | if (!err) |
48 | err = vas_reconfig_capabilties(type: caps->win_type, new_nr_creds: creds); |
49 | |
50 | if (err) |
51 | return -EINVAL; |
52 | |
53 | pr_info("Set QoS total credits %u\n" , creds); |
54 | |
55 | return count; |
56 | } |
57 | |
58 | #define sysfs_caps_entry_read(_name) \ |
59 | static ssize_t _name##_show(struct vas_cop_feat_caps *caps, char *buf) \ |
60 | { \ |
61 | return sprintf(buf, "%d\n", atomic_read(&caps->_name)); \ |
62 | } |
63 | |
64 | struct vas_sysfs_entry { |
65 | struct attribute attr; |
66 | ssize_t (*show)(struct vas_cop_feat_caps *, char *); |
67 | ssize_t (*store)(struct vas_cop_feat_caps *, const char *, size_t); |
68 | }; |
69 | |
70 | #define VAS_ATTR_RO(_name) \ |
71 | sysfs_caps_entry_read(_name); \ |
72 | static struct vas_sysfs_entry _name##_attribute = __ATTR(_name, \ |
73 | 0444, _name##_show, NULL); |
74 | |
75 | /* |
76 | * Create sysfs interface: |
77 | * /sys/devices/virtual/misc/vas/vas0/gzip/default_capabilities |
78 | * This directory contains the following VAS GZIP capabilities |
79 | * for the default credit type. |
80 | * /sys/devices/virtual/misc/vas/vas0/gzip/default_capabilities/nr_total_credits |
81 | * Total number of default credits assigned to the LPAR which |
82 | * can be changed with DLPAR operation. |
83 | * /sys/devices/virtual/misc/vas/vas0/gzip/default_capabilities/nr_used_credits |
84 | * Number of credits used by the user space. One credit will |
85 | * be assigned for each window open. |
86 | * |
87 | * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities |
88 | * This directory contains the following VAS GZIP capabilities |
89 | * for the Quality of Service (QoS) credit type. |
90 | * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities/nr_total_credits |
91 | * Total number of QoS credits assigned to the LPAR. The user |
92 | * has to define this value using HMC interface. It can be |
93 | * changed dynamically by the user. |
94 | * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities/nr_used_credits |
95 | * Number of credits used by the user space. |
96 | * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities/update_total_credits |
97 | * Update total QoS credits dynamically |
98 | */ |
99 | |
100 | VAS_ATTR_RO(nr_total_credits); |
101 | VAS_ATTR_RO(nr_used_credits); |
102 | |
103 | static struct vas_sysfs_entry update_total_credits_attribute = |
104 | __ATTR(update_total_credits, 0200, NULL, update_total_credits_store); |
105 | |
106 | static struct attribute *vas_def_capab_attrs[] = { |
107 | &nr_total_credits_attribute.attr, |
108 | &nr_used_credits_attribute.attr, |
109 | NULL, |
110 | }; |
111 | ATTRIBUTE_GROUPS(vas_def_capab); |
112 | |
113 | static struct attribute *vas_qos_capab_attrs[] = { |
114 | &nr_total_credits_attribute.attr, |
115 | &nr_used_credits_attribute.attr, |
116 | &update_total_credits_attribute.attr, |
117 | NULL, |
118 | }; |
119 | ATTRIBUTE_GROUPS(vas_qos_capab); |
120 | |
121 | static ssize_t vas_type_show(struct kobject *kobj, struct attribute *attr, |
122 | char *buf) |
123 | { |
124 | struct vas_caps_entry *centry; |
125 | struct vas_cop_feat_caps *caps; |
126 | struct vas_sysfs_entry *entry; |
127 | |
128 | centry = to_caps_entry(kobj); |
129 | caps = centry->caps; |
130 | entry = container_of(attr, struct vas_sysfs_entry, attr); |
131 | |
132 | if (!entry->show) |
133 | return -EIO; |
134 | |
135 | return entry->show(caps, buf); |
136 | } |
137 | |
138 | static ssize_t vas_type_store(struct kobject *kobj, struct attribute *attr, |
139 | const char *buf, size_t count) |
140 | { |
141 | struct vas_caps_entry *centry; |
142 | struct vas_cop_feat_caps *caps; |
143 | struct vas_sysfs_entry *entry; |
144 | |
145 | centry = to_caps_entry(kobj); |
146 | caps = centry->caps; |
147 | entry = container_of(attr, struct vas_sysfs_entry, attr); |
148 | if (!entry->store) |
149 | return -EIO; |
150 | |
151 | return entry->store(caps, buf, count); |
152 | } |
153 | |
154 | static void vas_type_release(struct kobject *kobj) |
155 | { |
156 | struct vas_caps_entry *centry = to_caps_entry(kobj); |
157 | kfree(objp: centry); |
158 | } |
159 | |
160 | static const struct sysfs_ops vas_sysfs_ops = { |
161 | .show = vas_type_show, |
162 | .store = vas_type_store, |
163 | }; |
164 | |
165 | static struct kobj_type vas_def_attr_type = { |
166 | .release = vas_type_release, |
167 | .sysfs_ops = &vas_sysfs_ops, |
168 | .default_groups = vas_def_capab_groups, |
169 | }; |
170 | |
171 | static struct kobj_type vas_qos_attr_type = { |
172 | .release = vas_type_release, |
173 | .sysfs_ops = &vas_sysfs_ops, |
174 | .default_groups = vas_qos_capab_groups, |
175 | }; |
176 | |
177 | static char *vas_caps_kobj_name(struct vas_caps_entry *centry, |
178 | struct kobject **kobj) |
179 | { |
180 | struct vas_cop_feat_caps *caps = centry->caps; |
181 | |
182 | if (caps->descriptor == VAS_GZIP_QOS_CAPABILITIES) { |
183 | kobject_init(kobj: ¢ry->kobj, ktype: &vas_qos_attr_type); |
184 | *kobj = gzip_caps_kobj; |
185 | return "qos_capabilities" ; |
186 | } else if (caps->descriptor == VAS_GZIP_DEFAULT_CAPABILITIES) { |
187 | kobject_init(kobj: ¢ry->kobj, ktype: &vas_def_attr_type); |
188 | *kobj = gzip_caps_kobj; |
189 | return "default_capabilities" ; |
190 | } else |
191 | return "Unknown" ; |
192 | } |
193 | |
194 | /* |
195 | * Add feature specific capability dir entry. |
196 | * Ex: VDefGzip or VQosGzip |
197 | */ |
198 | int sysfs_add_vas_caps(struct vas_cop_feat_caps *caps) |
199 | { |
200 | struct vas_caps_entry *centry; |
201 | struct kobject *kobj = NULL; |
202 | int ret = 0; |
203 | char *name; |
204 | |
205 | centry = kzalloc(size: sizeof(*centry), GFP_KERNEL); |
206 | if (!centry) |
207 | return -ENOMEM; |
208 | |
209 | centry->caps = caps; |
210 | name = vas_caps_kobj_name(centry, kobj: &kobj); |
211 | |
212 | if (kobj) { |
213 | ret = kobject_add(kobj: ¢ry->kobj, parent: kobj, fmt: "%s" , name); |
214 | |
215 | if (ret) { |
216 | pr_err("VAS: sysfs kobject add / event failed %d\n" , |
217 | ret); |
218 | kobject_put(kobj: ¢ry->kobj); |
219 | } |
220 | } |
221 | |
222 | return ret; |
223 | } |
224 | |
225 | static struct miscdevice vas_miscdev = { |
226 | .minor = MISC_DYNAMIC_MINOR, |
227 | .name = "vas" , |
228 | }; |
229 | |
230 | /* |
231 | * Add VAS and VasCaps (overall capabilities) dir entries. |
232 | */ |
233 | int __init sysfs_pseries_vas_init(struct vas_all_caps *vas_caps) |
234 | { |
235 | int ret; |
236 | |
237 | ret = misc_register(misc: &vas_miscdev); |
238 | if (ret < 0) { |
239 | pr_err("%s: register vas misc device failed\n" , __func__); |
240 | return ret; |
241 | } |
242 | |
243 | /* |
244 | * The hypervisor does not expose multiple VAS instances, but can |
245 | * see multiple VAS instances on PowerNV. So create 'vas0' directory |
246 | * on pseries. |
247 | */ |
248 | pseries_vas_kobj = kobject_create_and_add(name: "vas0" , |
249 | parent: &vas_miscdev.this_device->kobj); |
250 | if (!pseries_vas_kobj) { |
251 | misc_deregister(misc: &vas_miscdev); |
252 | pr_err("Failed to create VAS sysfs entry\n" ); |
253 | return -ENOMEM; |
254 | } |
255 | |
256 | if ((vas_caps->feat_type & VAS_GZIP_QOS_FEAT_BIT) || |
257 | (vas_caps->feat_type & VAS_GZIP_DEF_FEAT_BIT)) { |
258 | gzip_caps_kobj = kobject_create_and_add(name: "gzip" , |
259 | parent: pseries_vas_kobj); |
260 | if (!gzip_caps_kobj) { |
261 | pr_err("Failed to create VAS GZIP capability entry\n" ); |
262 | kobject_put(kobj: pseries_vas_kobj); |
263 | misc_deregister(misc: &vas_miscdev); |
264 | return -ENOMEM; |
265 | } |
266 | } |
267 | |
268 | return 0; |
269 | } |
270 | |
271 | #else |
272 | int sysfs_add_vas_caps(struct vas_cop_feat_caps *caps) |
273 | { |
274 | return 0; |
275 | } |
276 | |
277 | int __init sysfs_pseries_vas_init(struct vas_all_caps *vas_caps) |
278 | { |
279 | return 0; |
280 | } |
281 | #endif |
282 | |