blob: 9194070356ce92690dd8d50819de964dfa3c4567 [file] [log] [blame]
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");