Introducing the vSMP guest driver which allows interaction with the vSMP control device when running a Linux OS atop of the vSMP hypervisor. vSMP is a resource aggregation hypervisor from SAP. the driver comprises of 3 modules, vsmp which includes all the api needed to interact with the control driver, vsmp_logs which allows reading logs from the hypervisor and vsmp_common_info which allows reading generic information the hypervisor exposes, currently only the version is exposed. Signed-off-by: Eial Czerwacki <eial.czerwacki@xxxxxxx> v1 -> v2: - fix multiple rejects pointed out by Greg KH - fix multiple rejects pointed out by Dan Carpenter - fix multiple rejects pointed out by Randy Dunlap --- .../ABI/stable/sysfs-driver-vsmp_common_info | 5 + .../ABI/stable/sysfs-driver-vsmp_logs | 5 + drivers/virt/vsmp/Kconfig | 14 + drivers/virt/vsmp/Makefile | 7 + drivers/virt/vsmp/api.c | 416 ++++++++++++++++++ drivers/virt/vsmp/api.h | 61 +++ drivers/virt/vsmp/common/Kconfig | 11 + drivers/virt/vsmp/common/Makefile | 7 + drivers/virt/vsmp/common/common.c | 66 +++ drivers/virt/vsmp/common/common.h | 27 ++ drivers/virt/vsmp/common/version.c | 117 +++++ drivers/virt/vsmp/logs/Kconfig | 10 + drivers/virt/vsmp/logs/Makefile | 7 + drivers/virt/vsmp/logs/active_logs.c | 121 +++++ drivers/virt/vsmp/registers.h | 16 + 15 files changed, 890 insertions(+) create mode 100644 Documentation/ABI/stable/sysfs-driver-vsmp_common_info create mode 100644 Documentation/ABI/stable/sysfs-driver-vsmp_logs create mode 100644 drivers/virt/vsmp/Kconfig create mode 100644 drivers/virt/vsmp/Makefile create mode 100644 drivers/virt/vsmp/api.c create mode 100644 drivers/virt/vsmp/api.h create mode 100644 drivers/virt/vsmp/common/Kconfig create mode 100644 drivers/virt/vsmp/common/Makefile create mode 100644 drivers/virt/vsmp/common/common.c create mode 100644 drivers/virt/vsmp/common/common.h create mode 100644 drivers/virt/vsmp/common/version.c create mode 100644 drivers/virt/vsmp/logs/Kconfig create mode 100644 drivers/virt/vsmp/logs/Makefile create mode 100644 drivers/virt/vsmp/logs/active_logs.c create mode 100644 drivers/virt/vsmp/registers.h diff --git a/Documentation/ABI/stable/sysfs-driver-vsmp_common_info b/Documentation/ABI/stable/sysfs-driver-vsmp_common_info new file mode 100644 index 000000000000..a8a6afe417f3 --- /dev/null +++ b/Documentation/ABI/stable/sysfs-driver-vsmp_common_info @@ -0,0 +1,5 @@ +What: /sys/hypervisor/vsmp/version +Date: Apr 2022 +Contact: Eial Czerwacki <eial.czerwacki@xxxxxxx> + linux-vsmp@xxxxxxx +Description: Shows the full version of the vSMP hypervisor diff --git a/Documentation/ABI/stable/sysfs-driver-vsmp_logs b/Documentation/ABI/stable/sysfs-driver-vsmp_logs new file mode 100644 index 000000000000..a863898f5356 --- /dev/null +++ b/Documentation/ABI/stable/sysfs-driver-vsmp_logs @@ -0,0 +1,5 @@ +What: /sys/hypervisor/vsmp/logs +Date: Apr 2022 +Contact: Eial Czerwacki <eial.czerwacki@xxxxxxx> + linux-vsmp@xxxxxxx +Description: Dumps the logs of the current session from the vSMP hypervisor diff --git a/drivers/virt/vsmp/Kconfig b/drivers/virt/vsmp/Kconfig new file mode 100644 index 000000000000..e75879435769 --- /dev/null +++ b/drivers/virt/vsmp/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig VSMP + tristate "vSMP Guest Support" + depends on SYS_HYPERVISOR && X86_64 && PCI + 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. + +source "drivers/virt/vsmp/common/Kconfig" +source "drivers/virt/vsmp/logs/Kconfig" diff --git a/drivers/virt/vsmp/Makefile b/drivers/virt/vsmp/Makefile new file mode 100644 index 000000000000..88625cd3707d --- /dev/null +++ b/drivers/virt/vsmp/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for vSMP Guest drivers. +# + +obj-$(CONFIG_VSMP) += vsmp.o common/ logs/ +vsmp-y := api.o diff --git a/drivers/virt/vsmp/api.c b/drivers/virt/vsmp/api.c new file mode 100644 index 000000000000..57c345eaaa0c --- /dev/null +++ b/drivers/virt/vsmp/api.c @@ -0,0 +1,416 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP api + * Copyright (C) 2022 SAP. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci_regs.h> +#include <linux/pci.h> +#include <linux/kobject.h> +#include <linux/kernel.h> + +#include <asm/io.h> + +#include "api.h" + +#define DEVICE_NAME "vsmp" +#define DRIVER_LICENSE "GPL v2" +#define DRIVER_AUTHOR "Eial Czerwacki <eial.czerwacki@xxxxxxx>" +#define DRIVER_DESC "vSMP api driver" +#define DRIVER_VERSION "0.1" + +#define PCI_DEVICE_ID_SAP_FLX_VSMP_CTL 0x1011 +#define VSMP_CTL_MAX_PCI_BARS 2 + +/* modules info */ +MODULE_LICENSE(DRIVER_LICENSE); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); + +static unsigned int vsmp_bus; +static unsigned short vsmp_dev = 0x1f; +static unsigned char vsmp_func; +static bool vsmp_bdf_static = false; +static char *vsmp_ctrl_pci_addr = ""; +module_param(vsmp_ctrl_pci_addr, charp, 0660); + +/* defines */ +// copied from arch/x86/pci/direct.c +#define PCI_CONF1_ADDRESS(bus, devfn, reg) (0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \ + | (devfn << 8) | (reg & 0xFC)) + +/* global vars */ +void __iomem *cfg_addr; +static struct kobject *vsmp_sysfs_kobj; +static struct pci_dev *vsmp_dev_obj; + +static const struct pci_device_id vsmp_pci_tbl[] = { + { PCI_VDEVICE(SCALEMP, PCI_DEVICE_ID_SAP_FLX_VSMP_CTL), 0, }, + { 0, }, /* terminate list */ +}; + +static struct { + uint64_t start; + uint64_t len; +} mapped_bars[VSMP_CTL_MAX_PCI_BARS] = { 0 }; + +/* internal functions */ + +/* + * parses the pci address of a given address where the vSMP + * pci device is found + */ +static void parse_vsmp_ctrl_pci_addr(void) +{ + unsigned int bus; + unsigned short dev; + unsigned char func; + + if (!strlen(vsmp_ctrl_pci_addr)) + return; + + if (sscanf(vsmp_ctrl_pci_addr, "%04x:%02hx.%1hhx", &bus, &dev, &func) < 3) + return; + + vsmp_bus = bus; + vsmp_dev = dev; + vsmp_func = func; + vsmp_bdf_static = true; +} + +/* + * queries as specific device in order to determine if it is a vSMP PCI device + */ +static void query_pci_bus_for_vsmp_directly(void) + +{ + unsigned int devfn = PCI_DEVFN(vsmp_dev, vsmp_func); + printk(KERN_INFO + "vSMP pci controller not found in devices tree, trying directly at %x:%x.%x...\n", + vsmp_bus, vsmp_dev, vsmp_func); + + outl(PCI_CONF1_ADDRESS(vsmp_bus, devfn, PCI_BASE_ADDRESS_0), 0xcf8); + mapped_bars[0].start = inl(0xcfc); + + // define a knwon value which might be enough and backwars compataible + mapped_bars[0].len = 0x100000; +} + +/* + * scan all the devices on the system in order to locate the + * vSMP PCI device for a given dev end func is provided + */ +static void scan_pci_devs_list_for_static_vsmp(unsigned int static_devfn) +{ + struct pci_dev *pdev = NULL; + + for_each_pci_dev(pdev) { + if ((pdev->bus->number == vsmp_bus) && + (pdev->devfn == static_devfn)) { + vsmp_dev_obj = pdev; + break; + } + } +} + +/* + * open the cfg address space of the vSDP device + */ +static int open_cfg_addr(struct pci_dev *pdev) +{ + unsigned int static_devfn = PCI_DEVFN(vsmp_dev, vsmp_func); + int i; + + if (vsmp_bdf_static) { + scan_pci_devs_list_for_static_vsmp(static_devfn); + if (!vsmp_dev_obj) { + printk(KERN_INFO "vSMP pci controller not found, exiting.\n"); + return -ENODEV; + } + } else + vsmp_dev_obj = pdev; + + for (i = 0; i < ARRAY_SIZE(mapped_bars); i++) { + mapped_bars[i].start = pci_resource_start(vsmp_dev_obj, i); + mapped_bars[i].len = pci_resource_len(vsmp_dev_obj, i); + } + + if (!mapped_bars[0].start) + query_pci_bus_for_vsmp_directly(); + + if (!mapped_bars[0].len) { + printk(KERN_INFO "vSMP pci controller not found, exiting.\n"); + return -EINVAL; + } + + printk(KERN_INFO "mapping bar 0: [0x%llx,0x%llx]\n", + mapped_bars[0].start, mapped_bars[0].start + mapped_bars[0].len); + cfg_addr = ioremap(mapped_bars[0].start, mapped_bars[0].len); + + if (!cfg_addr) { + printk(KERN_ERR + "failed to map vSMP pci controller at %x:%x.%x, exiting.\n", + vsmp_bus, vsmp_dev, vsmp_func); + return -ENODEV; + } + + return 0; +} + +/* exported functions */ + +/* + * read a value from a specific register in the vSMP's device config space + */ +u64 vsmp_read_reg_from_cfg(u64 reg, enum reg_size_type type) +{ + u64 ret_val; + + switch (type) { + case VSMP_CTL_REG_SIZE_8BIT: + ret_val = readb(cfg_addr + reg); + break; + + case VSMP_CTL_REG_SIZE_16BIT: + ret_val = readw(cfg_addr + reg); + break; + + case VSMP_CTL_REG_SIZE_32BIT: + ret_val = readl(cfg_addr + reg); + break; + + case VSMP_CTL_REG_SIZE_64BIT: + ret_val = readq(cfg_addr + reg); + break; + + default: + printk(KERN_ERR "unsupported reg size type %d.\n", type); + ret_val = (u64) -EINVAL; + } + + dev_dbg(&vsmp_dev_obj->dev, "%s: read 0x%llx from reg 0x%llx of %d bits\n", + __func__, ret_val, reg, (type + 1) * 8); + return ret_val; +} +EXPORT_SYMBOL_GPL(vsmp_read_reg_from_cfg); + +/* + * write a value to a specific register in the vSMP's device config space + */ +void vsmp_write_reg_to_cfg(u64 reg, u64 value, + enum reg_size_type type) +{ + switch (type) { + case VSMP_CTL_REG_SIZE_8BIT: + writeb((u8) value, cfg_addr + reg); + break; + + case VSMP_CTL_REG_SIZE_16BIT: + writew((u16) value, cfg_addr + reg); + break; + + case VSMP_CTL_REG_SIZE_32BIT: + writel((u32) value, cfg_addr + reg); + break; + + case VSMP_CTL_REG_SIZE_64BIT: + writeq(value, cfg_addr + reg); + break; + + default: + printk(KERN_ERR "unsupported reg size type %d.\n", type); + } + + dev_dbg(&vsmp_dev_obj->dev, "%s: wrote 0x%llx to reg 0x%llx of %u bits\n", + __func__, value, reg, (type + 1) * 8); +} +EXPORT_SYMBOL_GPL(vsmp_write_reg_to_cfg); + +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 + */ +ssize_t vsmp_read_buff_from_bar(u8 bar, u32 offset, + char *out, ssize_t len, + bool halt_on_null) +{ + u8 __iomem *buff; + + BUG_ON(!mapped_bars[bar].start); + BUG_ON(ARRAY_SIZE(mapped_bars) <= bar); + BUG_ON((offset + len) > mapped_bars[bar].len); + + buff = ioremap(mapped_bars[bar].start + offset, 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; +} +EXPORT_SYMBOL_GPL(vsmp_read_buff_from_bar); + +/* + * register the vSMP sysfs object for user space interaction + */ +int vsmp_register_sysfs_group(const struct bin_attribute *bin_attr) +{ + int error = -EINVAL; + + if (vsmp_sysfs_kobj && bin_attr) { + error = sysfs_create_bin_file(vsmp_sysfs_kobj, bin_attr); + if (error) + printk(KERN_CRIT "%s returned error %d\n", __func__, + error); + } + + return error; +} +EXPORT_SYMBOL_GPL(vsmp_register_sysfs_group); + +/* + * deregister the vSMP sysfs object for user space interaction + */ +void vsmp_deregister_sysfs_group(const struct bin_attribute *bin_attr) +{ + if (vsmp_sysfs_kobj && bin_attr) + sysfs_remove_bin_file(vsmp_sysfs_kobj, bin_attr); +} +EXPORT_SYMBOL_GPL(vsmp_deregister_sysfs_group); + +/* + * generic function to read from the bar + */ +ssize_t vsmp_generic_buff_read(struct fw_ops *op, u8 bar, u64 reg, + char *buf, loff_t off, ssize_t count) +{ + ssize_t ret_val = 0; + + if (op->buff_offset >= op->hwi_block_size) { // perform H/W op + vsmp_reset_op(op); + + ret_val = vsmp_read_buff_from_bar(bar, reg, op->buff, op->hwi_block_size, false); + if (ret_val) { + printk(KERN_ERR "%s operation failed\n", + (op->action == FW_READ) ? "read" : "write"); + } + op->buff_offset = 0; + } + + if (ret_val) + return ret_val; + + return memory_read_from_buffer(buf, count, &op->buff_offset, op->buff, op->hwi_block_size);; +} +EXPORT_SYMBOL_GPL(vsmp_generic_buff_read); + +void vsmp_reset_op(struct fw_ops *op) +{ + memset(op->buff, 0, op->hwi_block_size); + op->buff_offset = op->hwi_block_size; +} +EXPORT_SYMBOL_GPL(vsmp_reset_op); + +int vsmp_init_op(struct fw_ops *op, ssize_t max_size, + enum vsmp_fw_action action) +{ + op->hwi_block_size = max_size; + op->action = action; + op->buff_offset = op->hwi_block_size; + + op->buff = kzalloc(op->hwi_block_size, GFP_KERNEL); + if (!op->buff) + return -ENOMEM; + + vsmp_reset_op(op); + + return 0; +} +EXPORT_SYMBOL_GPL(vsmp_init_op); + +void vsmp_release_op(struct fw_ops *op) +{ + BUG_ON(!op->buff); + kfree(op->buff); + memset(op, 0, sizeof(*op)); +} +EXPORT_SYMBOL_GPL(vsmp_release_op); + +bool vsmp_op_buffer_depleted(struct fw_ops *op) +{ + return op->buff_offset >= op->hwi_block_size; +} +EXPORT_SYMBOL_GPL(vsmp_op_buffer_depleted); + +/* module functions */ + +/* + * init the module + */ +static int vsmp_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + int ret_val; + + parse_vsmp_ctrl_pci_addr(); + + ret_val = open_cfg_addr(pci); + if (ret_val) { + printk(KERN_ERR "failed to open cfg addr for vSMP pci controller at %x:%x.%x, exiting.\n", + vsmp_bus, vsmp_dev, vsmp_func); + return ret_val; + } + + vsmp_sysfs_kobj = kobject_create_and_add("vsmp", hypervisor_kobj); + if (!vsmp_sysfs_kobj) { + printk(KERN_ERR "failed to create sysfs entry for vSMP pci controller at %x:%x.%x, exiting.\n", + vsmp_bus, vsmp_dev, vsmp_func); + return -ENODEV; + } + + printk(KERN_INFO "%s [%02x:%02x.%x] up and running\n", + DRIVER_DESC, vsmp_bus, vsmp_dev, vsmp_func); + + return 0; +} + +/* + * cleanup after the module upon exit + */ +static void vsmp_pci_remove(struct pci_dev *pci) +{ + if (cfg_addr) + iounmap(cfg_addr); + + if (vsmp_sysfs_kobj) + kobject_put(vsmp_sysfs_kobj); +} + +static struct pci_driver vsmp_pci_driver = { + .name = DEVICE_NAME, + .id_table = vsmp_pci_tbl, + .probe = vsmp_pci_probe, + .remove = vsmp_pci_remove, +}; + +module_pci_driver(vsmp_pci_driver); diff --git a/drivers/virt/vsmp/api.h b/drivers/virt/vsmp/api.h new file mode 100644 index 000000000000..4f5377cc9f72 --- /dev/null +++ b/drivers/virt/vsmp/api.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP api header + * Copyright (C) 2022 SAP. + */ + +#ifndef VSMP_API_H +#define VSMP_API_H + +#include <linux/sysfs.h> + +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 +}; + +enum vsmp_fw_action { + FW_READ = 0, + FW_WRITE = 1 +}; + +struct fw_ops { + enum vsmp_fw_action action; + ssize_t hwi_block_size; + unsigned char *buff; + loff_t buff_offset; + bool in_progress; +}; + +#define FILE_PREM (S_IRUSR | S_IRGRP | S_IROTH) + +#define VSMP_ATTR_RO(_name) static struct kobj_attribute _name##_attr = __ATTR_RO(_name) +#define vsmp_read_reg8_from_cfg(_reg_) ((u6) vsmp_read_reg_from_cfg((_reg_), VSMP_CTL_REG_SIZE_8BIT)) +#define vsmp_read_reg16_from_cfg(_reg_) ((u16) vsmp_read_reg_from_cfg((_reg_), VSMP_CTL_REG_SIZE_16BIT)) +#define vsmp_read_reg32_from_cfg(_reg_) ((u32) vsmp_read_reg_from_cfg((_reg_), VSMP_CTL_REG_SIZE_32BIT)) +#define vsmp_read_reg64_from_cfg(_reg_) ((u64) vsmp_read_reg_from_cfg((_reg_), VSMP_CTL_REG_SIZE_64BIT)) +#define vsmp_write_reg8_to_cfg(_reg_, _val_) vsmp_write_reg_to_cfg((_reg_),(_val_), VSMP_CTL_REG_SIZE_8BIT) +#define vsmp_write_reg16_to_cfg(_reg_, _val_) vsmp_write_reg_to_cfg((_reg_), (_val_), VSMP_CTL_REG_SIZE_16BIT) +#define vsmp_write_reg32_to_cfg(_reg_, _val_) vsmp_write_reg_to_cfg((_reg_), (_val_), VSMP_CTL_REG_SIZE_32BIT) +#define vsmp_write_reg64_to_cfg(_reg_, _val_) vsmp_write_reg_to_cfg((_reg_), (_val_), VSMP_CTL_REG_SIZE_64BIT) + +u64 vsmp_read_reg_from_cfg(u64 reg, enum reg_size_type type); +void vsmp_write_reg_to_cfg(u64 reg, u64 value, + enum reg_size_type type); +ssize_t vsmp_read_buff_from_bar(u8 bar, u32 offset, + char *out, ssize_t len, + bool halt_on_null); +int vsmp_register_sysfs_group(const struct bin_attribute *bin_attr); +void vsmp_deregister_sysfs_group(const struct bin_attribute *bin_attr); + +ssize_t vsmp_generic_buff_read(struct fw_ops *op, u8 bar, u64 reg, + char *buf, loff_t off, ssize_t count); +void vsmp_reset_op(struct fw_ops *op); +int vsmp_init_op(struct fw_ops *op, ssize_t max_size, + enum vsmp_fw_action action); +void vsmp_release_op(struct fw_ops *op); +bool vsmp_op_buffer_depleted(struct fw_ops *op); + +#endif // VSMP_API_H diff --git a/drivers/virt/vsmp/common/Kconfig b/drivers/virt/vsmp/common/Kconfig new file mode 100644 index 000000000000..5da4665acd38 --- /dev/null +++ b/drivers/virt/vsmp/common/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VSMP_COMMON + tristate "vSMP Guest driver common Support" + depends on VSMP + help + Select this feature in order to be able to retreive generic data from the vSMP + hypervisor. + Currently only version is supported. + The data can be found under /sys/hypervisor/vsmp + + If unsure, say N. diff --git a/drivers/virt/vsmp/common/Makefile b/drivers/virt/vsmp/common/Makefile new file mode 100644 index 000000000000..9db2f2389f8c --- /dev/null +++ b/drivers/virt/vsmp/common/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for vSMP Guest common feature. +# + +obj-$(CONFIG_VSMP_COMMON) += vsmp_common_info.o +vsmp_common_info-y += common.o version.o diff --git a/drivers/virt/vsmp/common/common.c b/drivers/virt/vsmp/common/common.c new file mode 100644 index 000000000000..4d88948b3029 --- /dev/null +++ b/drivers/virt/vsmp/common/common.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP common + * Copyright (C) 2022 SAP. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci_regs.h> +#include <linux/pci.h> +#include <linux/kobject.h> +#include <linux/kernel.h> + +#include <asm/io.h> + +#include "../api.h" +#include "common.h" + +#define DRIVER_LICENSE "GPL v2" +#define DRIVER_AUTHOR "Eial Czerwacki <eial.czerwacki@xxxxxxx>" +#define DRIVER_DESC "vSMP common info driver" +#define DRIVER_VERSION "0.1" + +/* modules info */ +MODULE_LICENSE(DRIVER_LICENSE); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); + +static struct sysfs_entry_cbs cbs_arr[] = { + create_entry(version), +}; + +/* module functions */ + +/* + * init the common module + */ +static int __init vsmp_common_info_init(void) +{ + int ret_val = -0, i; + + for (i = 0; (i < ARRAY_SIZE(cbs_arr) && !ret_val); i++) { + ret_val = cbs_arr[i].reg_cb(); + if (ret_val) { + printk(KERN_ERR "failed to init sysfs entry %d (%d), exiting.\n", + i, ret_val); + } + } + + return ret_val; +} + +/* + * cleanup after the module + */ +static void __exit vsmp_common_info_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cbs_arr); i++) + cbs_arr[i].dereg_cb(); +} + +module_init(vsmp_common_info_init); +module_exit(vsmp_common_info_exit); diff --git a/drivers/virt/vsmp/common/common.h b/drivers/virt/vsmp/common/common.h new file mode 100644 index 000000000000..08a096628c95 --- /dev/null +++ b/drivers/virt/vsmp/common/common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP common header + * Copyright (C) 2022 SAP. + */ + +#ifndef VSMP_COMMON_H +#define VSMP_COMMON_H + +#define create_entry(_label_) \ + { \ + .reg_cb = sysfs_register_ ## _label_ ## _cb, \ + .dereg_cb = sysfs_deregister_ ## _label_ ## _cb, \ + } + +typedef int (*sysfs_register_cb)(void); +typedef void (*sysfs_deregister_cb)(void); + +struct sysfs_entry_cbs { + sysfs_register_cb reg_cb; + sysfs_deregister_cb dereg_cb; +}; + +int sysfs_register_version_cb(void); +void sysfs_deregister_version_cb(void); + +#endif // !VSMP_COMMON_H diff --git a/drivers/virt/vsmp/common/version.c b/drivers/virt/vsmp/common/version.c new file mode 100644 index 000000000000..9af47085fd1c --- /dev/null +++ b/drivers/virt/vsmp/common/version.c @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP version + * Copyright (C) 2022 SAP. + */ + +#include <linux/slab.h> +#include <linux/kobject.h> + +#include "../api.h" +#include "../registers.h" + +/* + * 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) + +static struct fw_ops op; + +static ssize_t version_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + s64 reg_val = vsmp_read_reg32_from_cfg(VSMP_VERSION_REG); + ssize_t ret_val; + + if (reg_val < 0) { + printk(KERN_ERR "failed to value of reg 0x%x\n", VSMP_VERSION_REG); + return 0; + } + + ret_val = vsmp_generic_buff_read(&op, 0, reg_val, buf, off, count); + if (ret_val < 0) { + printk(KERN_ERR "failed to read version (%ld)\n", ret_val); + return 0; + } + + buf[ret_val++] = '\n'; + + return ret_val; +} + +struct bin_attribute version_raw_attr = __BIN_ATTR(version, FILE_PREM, version_read, NULL, VERSION_MAX_LEN); + +/* + * retreive 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(void) +{ + ssize_t ret_val = 0; + u64 reg_val; + char *version_str = kzalloc(VERSION_MAX_LEN, GFP_ATOMIC | __GFP_NOWARN); + + if (!version_str) { + printk(KERN_ERR "failed to allocate buffer\n"); + return -ENOMEM; + } + + reg_val = vsmp_read_reg32_from_cfg(VSMP_VERSION_REG); + if (reg_val < 0) { + printk(KERN_ERR "failed to value of reg 0x%x\n", VSMP_VERSION_REG); + return 0; + } + + memset(version_str, 0, VERSION_MAX_LEN); + ret_val = vsmp_read_buff_from_bar(0, reg_val, version_str, VERSION_MAX_LEN, true); + if (ret_val) { + kfree(version_str); + printk(KERN_ERR "failed to read buffer from bar\n"); + return ret_val; + } + kfree(version_str); + + return strlen(version_str); +} + +/* + * register the version sysfs entry + */ +int sysfs_register_version_cb(void) +{ + ssize_t len = get_version_len(); + int ret_val; + + if (len < 1) { + printk(KERN_ERR "failed to init vSMP version module\n"); + return len; + } + version_raw_attr.size = len; + + if (vsmp_init_op(&op, version_raw_attr.size, FW_READ)) { + printk(KERN_ERR "failed to init vSMP version op\n"); + return -ENODEV; + } + + ret_val = vsmp_register_sysfs_group(&version_raw_attr); + if (ret_val) { + printk(KERN_ERR "failed to init vSMP version support\n"); + vsmp_release_op(&op); + } + + return ret_val; +} + +/* + * deregister the version sysfs entry + */ +void sysfs_deregister_version_cb(void) +{ + vsmp_deregister_sysfs_group(&version_raw_attr); + vsmp_release_op(&op); +} diff --git a/drivers/virt/vsmp/logs/Kconfig b/drivers/virt/vsmp/logs/Kconfig new file mode 100644 index 000000000000..02949c351b86 --- /dev/null +++ b/drivers/virt/vsmp/logs/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VSMP_LOGS + tristate "vSMP Guest driver logs Support" + depends on VSMP + help + Select this feature in order to be able to retreive logs from the vSMP + hypervisor. + The data can be found under /sys/hypervisor/logs + + If unsure, say N. diff --git a/drivers/virt/vsmp/logs/Makefile b/drivers/virt/vsmp/logs/Makefile new file mode 100644 index 000000000000..8170cf639b92 --- /dev/null +++ b/drivers/virt/vsmp/logs/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for vSMP Guest common feature. +# + +obj-$(CONFIG_VSMP_LOGS) += vsmp_logs.o +vsmp_logs-y += active_logs.o diff --git a/drivers/virt/vsmp/logs/active_logs.c b/drivers/virt/vsmp/logs/active_logs.c new file mode 100644 index 000000000000..0b8e9b60fb9b --- /dev/null +++ b/drivers/virt/vsmp/logs/active_logs.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP logs + * Copyright (C) 2022 SAP. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci_regs.h> +#include <linux/pci.h> +#include <linux/kobject.h> +#include <linux/kernel.h> +#include <linux/mutex.h> + +#include <asm/io.h> + +#include "../api.h" +#include "../registers.h" + +#define DRIVER_LICENSE "GPL v2" +#define DRIVER_AUTHOR "Eial Czerwacki <eial.czerwacki@xxxxxxx>" +#define DRIVER_DESC "vSMP active logs driver" +#define DRIVER_VERSION "0.1" + +#define LOGS_MASK (1 << 0) + +/* modules info */ +MODULE_LICENSE(DRIVER_LICENSE); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); + +static struct fw_ops op; +static unsigned active_log_start; +DEFINE_MUTEX(active_log_mutex); + +/* module functions */ +static ssize_t active_log_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + ssize_t ret_val = 0; + + if (!op.in_progress) { + ret_val = mutex_lock_interruptible(&active_log_mutex); + if (ret_val) { + printk(KERN_ERR + "error in atemmpt to lock mutex (%lx)\n", + ret_val); + return 0; + } + + vsmp_write_reg16_to_cfg(VSMP_LOGS_CTRL_REG, + vsmp_read_reg16_from_cfg + (VSMP_LOGS_CTRL_REG) | LOGS_MASK); + op.in_progress = true; + vsmp_write_reg64_to_cfg(VSMP_LOGS_STATUS_REG, 0); + } + + if (vsmp_op_buffer_depleted(&op)) { + if (!(vsmp_read_reg16_from_cfg(VSMP_LOGS_CTRL_REG) & LOGS_MASK)) { + vsmp_reset_op(&op); + op.in_progress = 0; + mutex_unlock(&active_log_mutex); + return ret_val; + } + } + + ret_val = vsmp_generic_buff_read(&op, 1, active_log_start, buf, off, count); + if (ret_val < 0) { + printk(KERN_ERR "error reading from buffer\n"); + mutex_unlock(&active_log_mutex); + return 0; + } + + if (vsmp_op_buffer_depleted(&op)) { + vsmp_write_reg64_to_cfg(VSMP_LOGS_STATUS_REG, + ~0LL & ~(1LL << 0)); + } + + return count; +} + +static struct bin_attribute active_log_raw_attr = __BIN_ATTR(log, FILE_PREM, active_log_read, NULL, 0); + +/* + * init the log module + */ +static int __init vsmp_active_logs_init(void) +{ + int ret_val; + + if (!vsmp_init_op(&op, vsmp_read_reg32_from_cfg(VSMP_LOGS_LEN_REG), + FW_READ)) { + printk(KERN_ERR "failed to init vSMP log op\n"); + return -ENODEV; + } + + active_log_start = vsmp_read_reg32_from_cfg(VSMP_LOGS_START_REG); + mutex_init(&active_log_mutex); + + ret_val = vsmp_register_sysfs_group(&active_log_raw_attr); + if (ret_val) { + printk(KERN_ERR "failed to init vSMP active logs support\n"); + vsmp_release_op(&op); + } + + return ret_val; +} + +/* + * cleanup after the module + */ +static void __exit vsmp_active_logs_exit(void) +{ + vsmp_deregister_sysfs_group(&active_log_raw_attr); + vsmp_release_op(&op); +} + +module_init(vsmp_active_logs_init); +module_exit(vsmp_active_logs_exit); diff --git a/drivers/virt/vsmp/registers.h b/drivers/virt/vsmp/registers.h new file mode 100644 index 000000000000..6cb75dfcccc6 --- /dev/null +++ b/drivers/virt/vsmp/registers.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * vSMP offsets + * Copyright (C) 2022 SAP. + */ + +#ifndef VSMP_REGSITERS_H +#define VSMP_REGSITERS_H + +#define VSMP_LOGS_CTRL_REG 0x22 +#define VSMP_LOGS_START_REG 0x30 +#define VSMP_LOGS_LEN_REG 0x34 +#define VSMP_LOGS_STATUS_REG 0x38 +#define VSMP_VERSION_REG 0x0c + +#endif // VSMP_REGSITERS_H -- 2.25.1