| From e7d31ee0349c4800a4da34de3319352370e0799a Mon Sep 17 00:00:00 2001 |
| From: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Date: Tue, 6 Sep 2022 15:57:53 +0200 |
| Subject: [PATCH] vsmp driver |
| |
| first cut to cleanup |
| --- |
| Documentation/ABI/stable/sysfs-driver-vsmp | 5 |
| MAINTAINERS | 6 |
| drivers/virt/Kconfig | 2 |
| drivers/virt/Makefile | 2 |
| drivers/virt/vsmp/Kconfig | 12 + |
| drivers/virt/vsmp/Makefile | 6 |
| drivers/virt/vsmp/vsmp.c | 294 +++++++++++++++++++++++++++++ |
| 7 files changed, 327 insertions(+) |
| create mode 100644 Documentation/ABI/stable/sysfs-driver-vsmp |
| create mode 100644 drivers/virt/vsmp/Kconfig |
| create mode 100644 drivers/virt/vsmp/Makefile |
| create mode 100644 drivers/virt/vsmp/vsmp.c |
| |
| --- /dev/null |
| +++ b/Documentation/ABI/stable/sysfs-driver-vsmp |
| @@ -0,0 +1,5 @@ |
| +What: /sys/hypervisor/vsmp/version |
| +Date: Aug 2022 |
| +Contact: Eial Czerwacki <eial.czerwacki@sap.com> |
| + linux-vsmp@sap.com |
| +Description: Shows the full version of the vSMP hypervisor |
| --- a/MAINTAINERS |
| +++ b/MAINTAINERS |
| @@ -22634,6 +22634,12 @@ F: lib/test_printf.c |
| F: lib/test_scanf.c |
| F: lib/vsprintf.c |
| |
| +VSMP GUEST DRIVER |
| +M: Eial Czerwacki <eial.czerwacki@sap.com> |
| +M: linux-vsmp@sap.com |
| +S: Maintained |
| +F: drivers/virt/vsmp |
| + |
| VT1211 HARDWARE MONITOR DRIVER |
| M: Juerg Haefliger <juergh@proton.me> |
| L: linux-hwmon@vger.kernel.org |
| --- a/drivers/virt/Kconfig |
| +++ b/drivers/virt/Kconfig |
| @@ -54,4 +54,6 @@ source "drivers/virt/coco/sev-guest/Kcon |
| |
| source "drivers/virt/coco/tdx-guest/Kconfig" |
| |
| +source "drivers/virt/vsmp/Kconfig" |
| + |
| endif |
| --- a/drivers/virt/Makefile |
| +++ b/drivers/virt/Makefile |
| @@ -12,3 +12,5 @@ obj-$(CONFIG_ACRN_HSM) += acrn/ |
| obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/ |
| obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/ |
| obj-$(CONFIG_INTEL_TDX_GUEST) += coco/tdx-guest/ |
| + |
| +obj-$(CONFIG_VSMP) += vsmp/ |
| --- /dev/null |
| +++ b/drivers/virt/vsmp/Kconfig |
| @@ -0,0 +1,12 @@ |
| +# SPDX-License-Identifier: GPL-2.0-only |
| +config VSMP |
| + tristate "vSMP Guest Support" |
| + depends on X86_64 && PCI |
| + select SYS_HYPERVISOR |
| + help |
| + Support for vSMP Guest Driver. |
| + |
| + This driver allows information gathering of data from the vSMP hypervisor when |
| + running on top of a vSMP-based hypervisor. |
| + |
| + If unsure, say no. |
| --- /dev/null |
| +++ b/drivers/virt/vsmp/Makefile |
| @@ -0,0 +1,6 @@ |
| +# SPDX-License-Identifier: GPL-2.0 |
| +# |
| +# Makefile for vSMP Guest driver |
| +# |
| + |
| +obj-$(CONFIG_VSMP) = vsmp.o |
| --- /dev/null |
| +++ b/drivers/virt/vsmp/vsmp.c |
| @@ -0,0 +1,294 @@ |
| +// SPDX-License-Identifier: GPL-2.0-only |
| +/* |
| + * vSMP driver |
| + * |
| + * (C) Copyright 2022 SAP SE |
| + * (C) Copyright 2022 Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| + */ |
| +#include <linux/module.h> |
| +#include <linux/slab.h> |
| +#include <linux/kobject.h> |
| +#include <linux/pci.h> |
| +#include <linux/sysfs.h> |
| + |
| +// R/W ops handlers |
| +#define vsmp_read_reg32_from_cfg(dev, _reg_) \ |
| + ((u32) vsmp_read_reg_from_cfg(dev, (_reg_), VSMP_CTL_REG_SIZE_32BIT)) |
| + |
| +enum reg_size_type { |
| + VSMP_CTL_REG_SIZE_8BIT = 0, |
| + VSMP_CTL_REG_SIZE_16BIT, |
| + VSMP_CTL_REG_SIZE_32BIT, |
| + VSMP_CTL_REG_SIZE_64BIT |
| +}; |
| + |
| +struct vsmp_dev { |
| + void __iomem *cfg_addr; |
| + struct kobject kobj; |
| + struct pci_dev *pdev; |
| +}; |
| + |
| +#define VSMP_VERSION_REG 0x0c |
| + |
| +/* |
| + * Read a value from a specific register in the vSMP's device config space |
| + */ |
| +static u64 vsmp_read_reg_from_cfg(struct vsmp_dev *vdev, u64 reg, enum reg_size_type type) |
| +{ |
| + u64 ret_val; |
| + |
| + switch (type) { |
| + case VSMP_CTL_REG_SIZE_8BIT: |
| + ret_val = readb(vdev->cfg_addr + reg); |
| + break; |
| + |
| + case VSMP_CTL_REG_SIZE_16BIT: |
| + ret_val = readw(vdev->cfg_addr + reg); |
| + break; |
| + |
| + case VSMP_CTL_REG_SIZE_32BIT: |
| + ret_val = readl(vdev->cfg_addr + reg); |
| + break; |
| + |
| + case VSMP_CTL_REG_SIZE_64BIT: |
| + ret_val = readq(vdev->cfg_addr + reg); |
| + break; |
| + |
| + default: |
| + dev_err(&vdev->pdev->dev, "Unsupported reg size type %d.\n", type); |
| + ret_val = (u64) -EINVAL; |
| + } |
| + |
| + dev_dbg(&vdev->pdev->dev, "%s: read 0x%llx from reg 0x%llx of %d bits\n", |
| + __func__, ret_val, reg, (type + 1) * 8); |
| + return ret_val; |
| +} |
| + |
| +/* |
| + * Read a buffer from the bar byte by byte for halt on |
| + * null termination. |
| + * Expected buffs are strings. |
| + */ |
| +static ssize_t read_buff_from_bar_in_bytes(char *out, u8 __iomem *buff, ssize_t len) |
| +{ |
| + u32 i; |
| + |
| + for (i = 0; i < len; i++) { |
| + out[i] = ioread8(&buff[i]); |
| + if (!out[i]) |
| + break; |
| + } |
| + |
| + return i; |
| +} |
| + |
| +/* |
| + * Read a buffer from a specific offset in a specific bar, |
| + * maxed to a predefined len size-wise from the vSMP device |
| + */ |
| +static int vsmp_read_buff_from_bar(struct vsmp_dev *vdev, u8 bar, u32 offset, |
| + char *out, ssize_t len, bool halt_on_null) |
| +{ |
| + u8 __iomem *buff; |
| + u64 bar_start = pci_resource_start(vdev->pdev, bar); |
| + u32 bar_len = pci_resource_len(vdev->pdev, bar); |
| + ssize_t actual_len = len; |
| + |
| + /* incase of overflow, warn and use max len possible */ |
| + if ((offset + len) > bar_len) { |
| + WARN_ON((offset + len) > actual_len); |
| + actual_len = bar_len - offset; |
| + dev_dbg(&vdev->pdev->dev, "%lu overflows bar len, using %ld len instead\n", len, actual_len); |
| + } |
| + |
| + buff = ioremap(bar_start + offset, actual_len); |
| + if (!buff) |
| + return -ENOMEM; |
| + |
| + if (halt_on_null) |
| + read_buff_from_bar_in_bytes(out, buff, len); |
| + else |
| + memcpy_fromio(out, buff, len); |
| + |
| + iounmap(buff); |
| + |
| + return 0; |
| +} |
| + |
| +/* |
| + * Generic function to read from the bar |
| + */ |
| +/* |
| + * This is the maximal possible length of the version which is a text string |
| + * the real len is usually much smaller, thus the driver uses this once to read |
| + * the version string and record it's actual len. |
| + * From that point and on, the actual len will be used in each call. |
| + */ |
| +#define VERSION_MAX_LEN (1 << 19) |
| + |
| +/* |
| + * Retrieve str in order to determine the proper length. |
| + * This is the best way to maintain backwards compatibility with all |
| + * vSMP versions. |
| + */ |
| +static ssize_t get_version_len(struct vsmp_dev *vdev) |
| +{ |
| + ssize_t len = 0; |
| + u64 reg_val = vsmp_read_reg32_from_cfg(vdev, VSMP_VERSION_REG); |
| + char *version_str = kzalloc(VERSION_MAX_LEN, GFP_KERNEL); |
| + |
| + if (!version_str) |
| + return len; |
| + |
| + if (vsmp_read_reg32_from_cfg(vdev, VSMP_VERSION_REG) < 0) { |
| + kfree(version_str); |
| + dev_err(&vdev->pdev->dev, "Failed to read value of reg 0x%x\n", VSMP_VERSION_REG); |
| + return len; |
| + } |
| + |
| + memset(version_str, 0, VERSION_MAX_LEN); |
| + if (vsmp_read_buff_from_bar(vdev, 0, reg_val, version_str, VERSION_MAX_LEN, true)) { |
| + kfree(version_str); |
| + dev_err(&vdev->pdev->dev, "Failed to read buffer from bar\n"); |
| + return len; |
| + } |
| + |
| + len = strlen(version_str); |
| + kfree(version_str); |
| + |
| + return len; |
| +} |
| + |
| +static ssize_t version1_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, char *buf) |
| +{ |
| + return sysfs_emit(buf, "1\n"); |
| +} |
| + |
| +static ssize_t version2_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, char *buf) |
| +{ |
| + return sysfs_emit(buf, "2\n"); |
| +} |
| + |
| +static ssize_t version3_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, char *buf) |
| +{ |
| + return sysfs_emit(buf, "3\n"); |
| +} |
| + |
| +static ssize_t version_len_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, char *buf) |
| +{ |
| + struct vsmp_dev *vdev = container_of(kobj, struct vsmp_dev, kobj); |
| + int length; |
| + |
| + length = (int)get_version_len(vdev); |
| + |
| + return sysfs_emit(buf, "%d\n", length); |
| +} |
| + |
| +static struct kobj_attribute version1 = __ATTR_RO(version1); |
| +static struct kobj_attribute version2 = __ATTR_RO(version2); |
| +static struct kobj_attribute version3 = __ATTR_RO(version3); |
| +static struct kobj_attribute version_len = __ATTR_RO(version_len); |
| + |
| +static struct attribute *vsmp_attrs[] = { |
| + &version1.attr, |
| + &version2.attr, |
| + &version3.attr, |
| + &version_len.attr, |
| +}; |
| +ATTRIBUTE_GROUPS(vsmp); |
| + |
| +static void vsmp_release(struct kobject *kobj) |
| +{ |
| + struct vsmp_dev *vdev = container_of(kobj, struct vsmp_dev, kobj); |
| + |
| + kfree(vdev); |
| +} |
| + |
| +static ssize_t vsmp_show(struct kobject *kobj, struct attribute *attr, char *buf) |
| +{ |
| + struct kobj_attribute *kobj_attr; |
| + |
| + kobj_attr = container_of(attr, struct kobj_attribute, attr); |
| + return kobj_attr->show(kobj, kobj_attr, buf); |
| +} |
| + |
| +static const struct sysfs_ops vsmp_ops = { |
| + .show = vsmp_show, |
| +}; |
| + |
| +static struct kobj_type vsmp_type = { |
| + .release = vsmp_release, |
| + .sysfs_ops = &vsmp_ops, |
| + .default_groups = vsmp_groups, |
| +}; |
| + |
| +static int vsmp_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) |
| +{ |
| + struct vsmp_dev *vdev; |
| + u64 cfg_start; |
| + u32 cfg_len; |
| + int retval; |
| + |
| + vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); |
| + if (!vdev) |
| + return -ENOMEM; |
| + |
| + vdev->pdev = pci; |
| + |
| + /* |
| + * Open the cfg address space of the vSDP device |
| + */ |
| + cfg_start = pci_resource_start(pci, 0); |
| + cfg_len = pci_resource_len(pci, 0); |
| + |
| + dev_dbg(&pci->dev, "Mapping bar 0: [0x%llx,0x%llx]\n", cfg_start, cfg_start + cfg_len); |
| + |
| + vdev->cfg_addr = ioremap(cfg_start, cfg_len); |
| + if (!vdev->cfg_addr) { |
| + dev_err(&pci->dev, "Failed to map vSMP pci controller, exiting.\n"); |
| + kfree(vdev); |
| + return -ENODEV; |
| + } |
| + |
| + pci_set_drvdata(pci, vdev); |
| + |
| + retval = kobject_init_and_add(&vdev->kobj, &vsmp_type, hypervisor_kobj, "vsmp"); |
| + if (retval) { |
| + kobject_put(&vdev->kobj); |
| + dev_err(&vdev->pdev->dev, "Failed to create vSMP sysfs entry, exiting.\n"); |
| + return -ENODEV; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static void vsmp_pci_remove(struct pci_dev *pci) |
| +{ |
| + struct vsmp_dev *vdev = pci_get_drvdata(pci); |
| + |
| + iounmap(vdev->cfg_addr); |
| + kobject_del(&vdev->kobj); |
| +} |
| + |
| +#define PCI_DEVICE_ID_SAP_FLX_VSMP_CTL 0x1011 |
| +static const struct pci_device_id vsmp_pci_table[] = { |
| + { PCI_VDEVICE(SCALEMP, PCI_DEVICE_ID_SAP_FLX_VSMP_CTL), 0, }, |
| + { 0, }, /* terminate list */ |
| +}; |
| + |
| +static struct pci_driver vsmp_pci_driver = { |
| + .name = "vSMP", |
| + .id_table = vsmp_pci_table, |
| + .probe = vsmp_pci_probe, |
| + .remove = vsmp_pci_remove, |
| +}; |
| + |
| +module_pci_driver(vsmp_pci_driver); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_AUTHOR("Eial Czerwacki <eial.czerwacki@sap.com>"); |
| +MODULE_DESCRIPTION("vSMP hypervisor driver"); |