| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright 2022-23 IBM Corp. |
| */ |
| |
| #define pr_fmt(fmt) "vas: " fmt |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/miscdevice.h> |
| #include <linux/kobject.h> |
| #include <linux/slab.h> |
| #include <linux/mm.h> |
| |
| #include "vas.h" |
| |
| #ifdef CONFIG_SYSFS |
| static struct kobject *pseries_vas_kobj; |
| static struct kobject *gzip_caps_kobj; |
| |
| struct vas_caps_entry { |
| struct kobject kobj; |
| struct vas_cop_feat_caps *caps; |
| }; |
| |
| #define to_caps_entry(entry) container_of(entry, struct vas_caps_entry, kobj) |
| |
| /* |
| * This function is used to get the notification from the drmgr when |
| * QoS credits are changed. |
| */ |
| static ssize_t update_total_credits_store(struct vas_cop_feat_caps *caps, |
| const char *buf, size_t count) |
| { |
| int err; |
| u16 creds; |
| |
| err = kstrtou16(buf, 0, &creds); |
| /* |
| * The user space interface from the management console |
| * notifies OS with the new QoS credits and then the |
| * hypervisor. So OS has to use this new credits value |
| * and reconfigure VAS windows (close or reopen depends |
| * on the credits available) instead of depending on VAS |
| * QoS capabilities from the hypervisor. |
| */ |
| if (!err) |
| err = vas_reconfig_capabilties(caps->win_type, creds); |
| |
| if (err) |
| return -EINVAL; |
| |
| pr_info("Set QoS total credits %u\n", creds); |
| |
| return count; |
| } |
| |
| #define sysfs_caps_entry_read(_name) \ |
| static ssize_t _name##_show(struct vas_cop_feat_caps *caps, char *buf) \ |
| { \ |
| return sprintf(buf, "%d\n", atomic_read(&caps->_name)); \ |
| } |
| |
| struct vas_sysfs_entry { |
| struct attribute attr; |
| ssize_t (*show)(struct vas_cop_feat_caps *, char *); |
| ssize_t (*store)(struct vas_cop_feat_caps *, const char *, size_t); |
| }; |
| |
| #define VAS_ATTR_RO(_name) \ |
| sysfs_caps_entry_read(_name); \ |
| static struct vas_sysfs_entry _name##_attribute = __ATTR(_name, \ |
| 0444, _name##_show, NULL); |
| |
| /* |
| * Create sysfs interface: |
| * /sys/devices/virtual/misc/vas/vas0/gzip/default_capabilities |
| * This directory contains the following VAS GZIP capabilities |
| * for the default credit type. |
| * /sys/devices/virtual/misc/vas/vas0/gzip/default_capabilities/nr_total_credits |
| * Total number of default credits assigned to the LPAR which |
| * can be changed with DLPAR operation. |
| * /sys/devices/virtual/misc/vas/vas0/gzip/default_capabilities/nr_used_credits |
| * Number of credits used by the user space. One credit will |
| * be assigned for each window open. |
| * |
| * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities |
| * This directory contains the following VAS GZIP capabilities |
| * for the Quality of Service (QoS) credit type. |
| * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities/nr_total_credits |
| * Total number of QoS credits assigned to the LPAR. The user |
| * has to define this value using HMC interface. It can be |
| * changed dynamically by the user. |
| * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities/nr_used_credits |
| * Number of credits used by the user space. |
| * /sys/devices/virtual/misc/vas/vas0/gzip/qos_capabilities/update_total_credits |
| * Update total QoS credits dynamically |
| */ |
| |
| VAS_ATTR_RO(nr_total_credits); |
| VAS_ATTR_RO(nr_used_credits); |
| |
| static struct vas_sysfs_entry update_total_credits_attribute = |
| __ATTR(update_total_credits, 0200, NULL, update_total_credits_store); |
| |
| static struct attribute *vas_def_capab_attrs[] = { |
| &nr_total_credits_attribute.attr, |
| &nr_used_credits_attribute.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(vas_def_capab); |
| |
| static struct attribute *vas_qos_capab_attrs[] = { |
| &nr_total_credits_attribute.attr, |
| &nr_used_credits_attribute.attr, |
| &update_total_credits_attribute.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(vas_qos_capab); |
| |
| static ssize_t vas_type_show(struct kobject *kobj, struct attribute *attr, |
| char *buf) |
| { |
| struct vas_caps_entry *centry; |
| struct vas_cop_feat_caps *caps; |
| struct vas_sysfs_entry *entry; |
| |
| centry = to_caps_entry(kobj); |
| caps = centry->caps; |
| entry = container_of(attr, struct vas_sysfs_entry, attr); |
| |
| if (!entry->show) |
| return -EIO; |
| |
| return entry->show(caps, buf); |
| } |
| |
| static ssize_t vas_type_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct vas_caps_entry *centry; |
| struct vas_cop_feat_caps *caps; |
| struct vas_sysfs_entry *entry; |
| |
| centry = to_caps_entry(kobj); |
| caps = centry->caps; |
| entry = container_of(attr, struct vas_sysfs_entry, attr); |
| if (!entry->store) |
| return -EIO; |
| |
| return entry->store(caps, buf, count); |
| } |
| |
| static void vas_type_release(struct kobject *kobj) |
| { |
| struct vas_caps_entry *centry = to_caps_entry(kobj); |
| kfree(centry); |
| } |
| |
| static const struct sysfs_ops vas_sysfs_ops = { |
| .show = vas_type_show, |
| .store = vas_type_store, |
| }; |
| |
| static struct kobj_type vas_def_attr_type = { |
| .release = vas_type_release, |
| .sysfs_ops = &vas_sysfs_ops, |
| .default_groups = vas_def_capab_groups, |
| }; |
| |
| static struct kobj_type vas_qos_attr_type = { |
| .release = vas_type_release, |
| .sysfs_ops = &vas_sysfs_ops, |
| .default_groups = vas_qos_capab_groups, |
| }; |
| |
| static char *vas_caps_kobj_name(struct vas_caps_entry *centry, |
| struct kobject **kobj) |
| { |
| struct vas_cop_feat_caps *caps = centry->caps; |
| |
| if (caps->descriptor == VAS_GZIP_QOS_CAPABILITIES) { |
| kobject_init(¢ry->kobj, &vas_qos_attr_type); |
| *kobj = gzip_caps_kobj; |
| return "qos_capabilities"; |
| } else if (caps->descriptor == VAS_GZIP_DEFAULT_CAPABILITIES) { |
| kobject_init(¢ry->kobj, &vas_def_attr_type); |
| *kobj = gzip_caps_kobj; |
| return "default_capabilities"; |
| } else |
| return "Unknown"; |
| } |
| |
| /* |
| * Add feature specific capability dir entry. |
| * Ex: VDefGzip or VQosGzip |
| */ |
| int sysfs_add_vas_caps(struct vas_cop_feat_caps *caps) |
| { |
| struct vas_caps_entry *centry; |
| struct kobject *kobj = NULL; |
| int ret = 0; |
| char *name; |
| |
| centry = kzalloc(sizeof(*centry), GFP_KERNEL); |
| if (!centry) |
| return -ENOMEM; |
| |
| centry->caps = caps; |
| name = vas_caps_kobj_name(centry, &kobj); |
| |
| if (kobj) { |
| ret = kobject_add(¢ry->kobj, kobj, "%s", name); |
| |
| if (ret) { |
| pr_err("VAS: sysfs kobject add / event failed %d\n", |
| ret); |
| kobject_put(¢ry->kobj); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static struct miscdevice vas_miscdev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "vas", |
| }; |
| |
| /* |
| * Add VAS and VasCaps (overall capabilities) dir entries. |
| */ |
| int __init sysfs_pseries_vas_init(struct vas_all_caps *vas_caps) |
| { |
| int ret; |
| |
| ret = misc_register(&vas_miscdev); |
| if (ret < 0) { |
| pr_err("%s: register vas misc device failed\n", __func__); |
| return ret; |
| } |
| |
| /* |
| * The hypervisor does not expose multiple VAS instances, but can |
| * see multiple VAS instances on PowerNV. So create 'vas0' directory |
| * on pseries. |
| */ |
| pseries_vas_kobj = kobject_create_and_add("vas0", |
| &vas_miscdev.this_device->kobj); |
| if (!pseries_vas_kobj) { |
| misc_deregister(&vas_miscdev); |
| pr_err("Failed to create VAS sysfs entry\n"); |
| return -ENOMEM; |
| } |
| |
| if ((vas_caps->feat_type & VAS_GZIP_QOS_FEAT_BIT) || |
| (vas_caps->feat_type & VAS_GZIP_DEF_FEAT_BIT)) { |
| gzip_caps_kobj = kobject_create_and_add("gzip", |
| pseries_vas_kobj); |
| if (!gzip_caps_kobj) { |
| pr_err("Failed to create VAS GZIP capability entry\n"); |
| kobject_put(pseries_vas_kobj); |
| misc_deregister(&vas_miscdev); |
| return -ENOMEM; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #else |
| int sysfs_add_vas_caps(struct vas_cop_feat_caps *caps) |
| { |
| return 0; |
| } |
| |
| int __init sysfs_pseries_vas_init(struct vas_all_caps *vas_caps) |
| { |
| return 0; |
| } |
| #endif |