From e8a60aa7404bfef37705da5607c97737073ac38d Mon Sep 17 00:00:00 2001 From: Divya Bharathi Date: Tue, 27 Oct 2020 19:19:44 +0530 Subject: platform/x86: Introduce support for Systems Management Driver over WMI for Dell Systems The Dell WMI Systems Management Driver provides a sysfs interface for systems management to enable BIOS configuration capability on certain Dell Systems. This driver allows user to configure Dell systems with a uniform common interface. To facilitate this, the patch introduces a generic way for driver to be able to create configurable BIOS Attributes available in Setup (F2) screen. Cc: Hans de Goede Cc: Andy Shevchenko Cc: mark gross Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Co-developed-by: Prasanth KSR Signed-off-by: Prasanth KSR Signed-off-by: Divya Bharathi Link: https://lore.kernel.org/r/20201027134944.316730-1-divya.bharathi@dell.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-class-firmware-attributes | 224 +++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-firmware-attributes (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes new file mode 100644 index 000000000000..04a15c72e883 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -0,0 +1,224 @@ +What: /sys/class/firmware-attributes/*/attributes/*/ +Date: February 2021 +KernelVersion: 5.11 +Contact: Divya Bharathi , + Mario Limonciello , + Prasanth KSR +Description: + A sysfs interface for systems management software to enable + configuration capability on supported systems. This directory + exposes interfaces for interacting with configuration options. + + Unless otherwise specified in an attribute description all attributes are optional + and will accept UTF-8 input. + + type: A file that can be read to obtain the type of attribute. This attribute is + mandatory. + + The following are known types: + - enumeration: a set of pre-defined valid values + - integer: a range of numerical values + - string + + All attribute types support the following values: + + current_value: A file that can be read to obtain the current + value of the . + + This file can also be written to in order to update the value of a + + + This attribute is mandatory. + + default_value: A file that can be read to obtain the default + value of the + + display_name: A file that can be read to obtain a user friendly + description of the at + + display_name_language_code: A file that can be read to obtain + the IETF language tag corresponding to the + "display_name" of the + + "enumeration"-type specific properties: + + possible_values: A file that can be read to obtain the possible + values of the . Values are separated using + semi-colon (``;``). + + "integer"-type specific properties: + + min_value: A file that can be read to obtain the lower + bound value of the + + max_value: A file that can be read to obtain the upper + bound value of the + + scalar_increment: A file that can be read to obtain the scalar value used for + increments of current_value this attribute accepts. + + "string"-type specific properties: + + max_length: A file that can be read to obtain the maximum + length value of the + + min_length: A file that can be read to obtain the minimum + length value of the + + Dell specific class extensions + -------------------------- + + On Dell systems the following additional attributes are available: + + dell_modifier: A file that can be read to obtain attribute-level + dependency rule. It says an attribute X will become read-only or + suppressed, if/if-not attribute Y is configured. + + modifier rules can be in following format: + [ReadOnlyIf:=] + [ReadOnlyIfNot:=] + [SuppressIf:=] + [SuppressIfNot:=] + + For example: + AutoOnFri/dell_modifier has value, + [SuppressIfNot:AutoOn=SelectDays] + + This means AutoOnFri will be suppressed in BIOS setup if AutoOn + attribute is not "SelectDays" and its value will not be effective + through sysfs until this rule is met. + + Enumeration attributes also support the following: + + dell_value_modifier: A file that can be read to obtain value-level dependency. + This file is similar to dell_modifier but here, an + attribute's current value will be forcefully changed based + dependent attributes value. + + dell_value_modifier rules can be in following format: + [ForceIf:=] + [ForceIfNot:=] + + For example, + LegacyOrom/dell_value_modifier has value: + Disabled[ForceIf:SecureBoot=Enabled] + This means LegacyOrom's current value will be forced to + "Disabled" in BIOS setup if SecureBoot is Enabled and its + value will not be effective through sysfs until this rule is + met. + +What: /sys/class/firmware-attributes/*/authentication/ +Date: February 2021 +KernelVersion: 5.11 +Contact: Divya Bharathi , + Mario Limonciello , + Prasanth KSR + + Devices support various authentication mechanisms which can be exposed + as a separate configuration object. + + For example a "BIOS Admin" password and "System" Password can be set, + reset or cleared using these attributes. + - An "Admin" password is used for preventing modification to the BIOS + settings. + - A "System" password is required to boot a machine. + + Change in any of these two authentication methods will also generate an + uevent KOBJ_CHANGE. + + is_enabled: A file that can be read to obtain a 0/1 flag to see if + authentication is enabled. + This attribute is mandatory. + + role: The type of authentication used. + This attribute is mandatory. + Known types: + bios-admin: Representing BIOS administrator password + power-on: Representing a password required to use + the system + + mechanism: The means of authentication. This attribute is mandatory. + Only supported type currently is "password". + + max_password_length: A file that can be read to obtain the + maximum length of the Password + + min_password_length: A file that can be read to obtain the + minimum length of the Password + + current_password: A write only value used for privileged access such as + setting attributes when a system or admin password is set + or resetting to a new password + + This attribute is mandatory when mechanism == "password". + + new_password: A write only value that when used in tandem with + current_password will reset a system or admin password. + + Note, password management is session specific. If Admin password is set, + same password must be written into current_password file (required for + password-validation) and must be cleared once the session is over. + For example: + echo "password" > current_password + echo "disabled" > TouchScreen/current_value + echo "" > current_password + + Drivers may emit a CHANGE uevent when a password is set or unset + userspace may check it again. + + On Dell systems, if Admin password is set, then all BIOS attributes + require password validation. + +What: /sys/class/firmware-attributes/*/attributes/pending_reboot +Date: February 2021 +KernelVersion: 5.11 +Contact: Divya Bharathi , + Mario Limonciello , + Prasanth KSR +Description: + A read-only attribute reads 1 if a reboot is necessary to apply + pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is + generated when it changes to 1. + + 0: All BIOS attributes setting are current + 1: A reboot is necessary to get pending BIOS attribute changes + applied + + Note, userspace applications need to follow below steps for efficient + BIOS management, + 1. Check if admin password is set. If yes, follow session method for + password management as briefed under authentication section above. + 2. Before setting any attribute, check if it has any modifiers + or value_modifiers. If yes, incorporate them and then modify + attribute. + + Drivers may emit a CHANGE uevent when this value changes and userspace + may check it again. + +What: /sys/class/firmware-attributes/*/attributes/reset_bios +Date: February 2021 +KernelVersion: 5.11 +Contact: Divya Bharathi , + Mario Limonciello , + Prasanth KSR +Description: + This attribute can be used to reset the BIOS Configuration. + Specifically, it tells which type of reset BIOS configuration is being + requested on the host. + + Reading from it returns a list of supported options encoded as: + + 'builtinsafe' (Built in safe configuration profile) + 'lastknowngood' (Last known good saved configuration profile) + 'factory' (Default factory settings configuration profile) + 'custom' (Custom saved configuration profile) + + The currently selected option is printed in square brackets as + shown below: + + # echo "factory" > /sys/class/firmware-attributes/*/device/attributes/reset_bios + # cat /sys/class/firmware-attributes/*/device/attributes/reset_bios + # builtinsafe lastknowngood [factory] custom + + Note that any changes to this attribute requires a reboot + for changes to take effect. -- cgit v1.2.3 From 5b09081f4c5c7159cb3789f35bb82bd9d32061c9 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 29 Oct 2020 10:31:23 +0100 Subject: docs: ABI: sysfs-class-firmware-attributes: solve some warnings The Description: tag is missing on some places, causing scripts/get_abi.pl warnings: Warning: file Documentation/ABI/testing/sysfs-class-firmware-attributes#172: What '/sys/class/firmware-attributes/*/authentication/' doesn't have a description Also, some warnings are produced when generating html documentation: .../Documentation/ABI/testing/sysfs-class-firmware-attributes:2: WARNING: Title underline too short. Dell specific class extensions -------------------------- .../Documentation/ABI/testing/sysfs-class-firmware-attributes:2: WARNING: Unexpected indentation. .../Documentation/ABI/testing/sysfs-class-firmware-attributes:2: WARNING: Unexpected indentation. .../Documentation/ABI/testing/sysfs-class-firmware-attributes:2: WARNING: Block quote ends without a blank line; unexpected unindent. .../Documentation/ABI/testing/sysfs-class-firmware-attributes:173: WARNING: Unexpected indentation. .../Documentation/ABI/testing/sysfs-class-firmware-attributes:173: WARNING: Unexpected indentation. .../Documentation/ABI/testing/sysfs-class-firmware-attributes:173: WARNING: Block quote ends without a blank line; unexpected unindent. .../Documentation/ABI/testing/sysfs-class-firmware-attributes:111: WARNING: Inline emphasis start-string without end-string. Address the warnings, making it to produce the expected output for the documentation ABI. Signed-off-by: Mauro Carvalho Chehab Link: https://lore.kernel.org/r/44b4181b4f772fcc5ec676e72b295c10df35121b.1603963862.git.mchehab+huawei@kernel.org Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-class-firmware-attributes | 140 +++++++++++++-------- 1 file changed, 87 insertions(+), 53 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 04a15c72e883..8ea59fea4709 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -12,17 +12,20 @@ Description: Unless otherwise specified in an attribute description all attributes are optional and will accept UTF-8 input. - type: A file that can be read to obtain the type of attribute. This attribute is - mandatory. + type: + A file that can be read to obtain the type of attribute. + This attribute is mandatory. The following are known types: + - enumeration: a set of pre-defined valid values - integer: a range of numerical values - string All attribute types support the following values: - current_value: A file that can be read to obtain the current + current_value: + A file that can be read to obtain the current value of the . This file can also be written to in order to update the value of a @@ -30,59 +33,71 @@ Description: This attribute is mandatory. - default_value: A file that can be read to obtain the default + default_value: + A file that can be read to obtain the default value of the - display_name: A file that can be read to obtain a user friendly + display_name: + A file that can be read to obtain a user friendly description of the at - display_name_language_code: A file that can be read to obtain + display_name_language_code: + A file that can be read to obtain the IETF language tag corresponding to the "display_name" of the "enumeration"-type specific properties: - possible_values: A file that can be read to obtain the possible + possible_values: + A file that can be read to obtain the possible values of the . Values are separated using semi-colon (``;``). "integer"-type specific properties: - min_value: A file that can be read to obtain the lower + min_value: + A file that can be read to obtain the lower bound value of the - max_value: A file that can be read to obtain the upper + max_value: + A file that can be read to obtain the upper bound value of the - scalar_increment: A file that can be read to obtain the scalar value used for + scalar_increment: + A file that can be read to obtain the scalar value used for increments of current_value this attribute accepts. "string"-type specific properties: - max_length: A file that can be read to obtain the maximum + max_length: + A file that can be read to obtain the maximum length value of the - min_length: A file that can be read to obtain the minimum + min_length: + A file that can be read to obtain the minimum length value of the Dell specific class extensions - -------------------------- + ------------------------------ On Dell systems the following additional attributes are available: - dell_modifier: A file that can be read to obtain attribute-level + dell_modifier: + A file that can be read to obtain attribute-level dependency rule. It says an attribute X will become read-only or suppressed, if/if-not attribute Y is configured. - modifier rules can be in following format: - [ReadOnlyIf:=] - [ReadOnlyIfNot:=] - [SuppressIf:=] - [SuppressIfNot:=] + modifier rules can be in following format:: + + [ReadOnlyIf:=] + [ReadOnlyIfNot:=] + [SuppressIf:=] + [SuppressIfNot:=] - For example: - AutoOnFri/dell_modifier has value, - [SuppressIfNot:AutoOn=SelectDays] + For example:: + + AutoOnFri/dell_modifier has value, + [SuppressIfNot:AutoOn=SelectDays] This means AutoOnFri will be suppressed in BIOS setup if AutoOn attribute is not "SelectDays" and its value will not be effective @@ -90,18 +105,22 @@ Description: Enumeration attributes also support the following: - dell_value_modifier: A file that can be read to obtain value-level dependency. + dell_value_modifier: + A file that can be read to obtain value-level dependency. This file is similar to dell_modifier but here, an attribute's current value will be forcefully changed based dependent attributes value. - dell_value_modifier rules can be in following format: - [ForceIf:=] - [ForceIfNot:=] + dell_value_modifier rules can be in following format:: + + [ForceIf:=] + [ForceIfNot:=] + + For example: + + LegacyOrom/dell_value_modifier has value: + Disabled[ForceIf:SecureBoot=Enabled] - For example, - LegacyOrom/dell_value_modifier has value: - Disabled[ForceIf:SecureBoot=Enabled] This means LegacyOrom's current value will be forced to "Disabled" in BIOS setup if SecureBoot is Enabled and its value will not be effective through sysfs until this rule is @@ -113,12 +132,13 @@ KernelVersion: 5.11 Contact: Divya Bharathi , Mario Limonciello , Prasanth KSR - +Description: Devices support various authentication mechanisms which can be exposed as a separate configuration object. For example a "BIOS Admin" password and "System" Password can be set, reset or cleared using these attributes. + - An "Admin" password is used for preventing modification to the BIOS settings. - A "System" password is required to boot a machine. @@ -126,39 +146,50 @@ Contact: Divya Bharathi , Change in any of these two authentication methods will also generate an uevent KOBJ_CHANGE. - is_enabled: A file that can be read to obtain a 0/1 flag to see if + is_enabled: + A file that can be read to obtain a 0/1 flag to see if authentication is enabled. This attribute is mandatory. - role: The type of authentication used. + role: + The type of authentication used. This attribute is mandatory. - Known types: - bios-admin: Representing BIOS administrator password - power-on: Representing a password required to use - the system - mechanism: The means of authentication. This attribute is mandatory. + Known types: + bios-admin: + Representing BIOS administrator password + power-on: + Representing a password required to use + the system + + mechanism: + The means of authentication. This attribute is mandatory. Only supported type currently is "password". - max_password_length: A file that can be read to obtain the + max_password_length: + A file that can be read to obtain the maximum length of the Password - min_password_length: A file that can be read to obtain the + min_password_length: + A file that can be read to obtain the minimum length of the Password - current_password: A write only value used for privileged access such as + current_password: + A write only value used for privileged access such as setting attributes when a system or admin password is set or resetting to a new password This attribute is mandatory when mechanism == "password". - new_password: A write only value that when used in tandem with + new_password: + A write only value that when used in tandem with current_password will reset a system or admin password. Note, password management is session specific. If Admin password is set, same password must be written into current_password file (required for password-validation) and must be cleared once the session is over. - For example: + For example:: + echo "password" > current_password echo "disabled" > TouchScreen/current_value echo "" > current_password @@ -180,12 +211,15 @@ Description: pending BIOS attribute changes. Also, an uevent_KOBJ_CHANGE is generated when it changes to 1. - 0: All BIOS attributes setting are current - 1: A reboot is necessary to get pending BIOS attribute changes - applied + == ========================================= + 0 All BIOS attributes setting are current + 1 A reboot is necessary to get pending BIOS + attribute changes applied + == ========================================= Note, userspace applications need to follow below steps for efficient BIOS management, + 1. Check if admin password is set. If yes, follow session method for password management as briefed under authentication section above. 2. Before setting any attribute, check if it has any modifiers @@ -208,17 +242,17 @@ Description: Reading from it returns a list of supported options encoded as: - 'builtinsafe' (Built in safe configuration profile) - 'lastknowngood' (Last known good saved configuration profile) - 'factory' (Default factory settings configuration profile) - 'custom' (Custom saved configuration profile) + - 'builtinsafe' (Built in safe configuration profile) + - 'lastknowngood' (Last known good saved configuration profile) + - 'factory' (Default factory settings configuration profile) + - 'custom' (Custom saved configuration profile) The currently selected option is printed in square brackets as - shown below: + shown below:: - # echo "factory" > /sys/class/firmware-attributes/*/device/attributes/reset_bios - # cat /sys/class/firmware-attributes/*/device/attributes/reset_bios - # builtinsafe lastknowngood [factory] custom + # echo "factory" > /sys/class/firmware-attributes/*/device/attributes/reset_bios + # cat /sys/class/firmware-attributes/*/device/attributes/reset_bios + # builtinsafe lastknowngood [factory] custom Note that any changes to this attribute requires a reboot for changes to take effect. -- cgit v1.2.3 From e2729113ce66d8d21f729b41bc3ed3feaf1acf69 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Wed, 28 Oct 2020 18:55:34 -0700 Subject: platform/x86: Intel PMT class driver Intel Platform Monitoring Technology is meant to provide a common way to access telemetry and system metrics. Register mappings are not provided by the driver. Instead, a GUID is read from a header for each endpoint. The GUID identifies the device and is to be used with an XML, provided by the vendor, to discover the available set of metrics and their register mapping. This allows firmware updates to modify the register space without needing to update the driver every time with new mappings. Firmware writes a new GUID in this case to specify the new mapping. Software tools with access to the associated XML file can then interpret the changes. The module manages access to all Intel PMT endpoints on a system, independent of the device exporting them. It creates an intel_pmt class to manage the devices. For each telemetry endpoint, sysfs files provide GUID and size information as well as a pointer to the parent device the telemetry came from. Software may discover the association between endpoints and devices by iterating through the list in sysfs, or by looking for the existence of the class folder under the device of interest. A binary sysfs attribute of the same name allows software to then read or map the telemetry space for direct access. Signed-off-by: Alexander Duyck Signed-off-by: David E. Box Reviewed-by: Hans de Goede Signed-off-by: Lee Jones --- Documentation/ABI/testing/sysfs-class-intel_pmt | 54 +++++ MAINTAINERS | 1 + drivers/platform/x86/Kconfig | 12 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/intel_pmt_class.c | 297 ++++++++++++++++++++++++ drivers/platform/x86/intel_pmt_class.h | 52 +++++ 6 files changed, 417 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-intel_pmt create mode 100644 drivers/platform/x86/intel_pmt_class.c create mode 100644 drivers/platform/x86/intel_pmt_class.h (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-class-intel_pmt b/Documentation/ABI/testing/sysfs-class-intel_pmt new file mode 100644 index 000000000000..926b5cf95fd1 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-intel_pmt @@ -0,0 +1,54 @@ +What: /sys/class/intel_pmt/ +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + The intel_pmt/ class directory contains information for + devices that expose hardware telemetry using Intel Platform + Monitoring Technology (PMT) + +What: /sys/class/intel_pmt/telem +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + The telem directory contains files describing an instance of + a PMT telemetry device that exposes hardware telemetry. Each + telem directory has an associated telem file. This file + may be opened and mapped or read to access the telemetry space + of the device. The register layout of the telemetry space is + determined from an XML file that matches the PCI device id and + GUID for the device. + +What: /sys/class/intel_pmt/telem/telem +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + (RO) The telemetry data for this telemetry device. This file + may be mapped or read to obtain the data. + +What: /sys/class/intel_pmt/telem/guid +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + (RO) The GUID for this telemetry device. The GUID identifies + the version of the XML file for the parent device that is to + be used to get the register layout. + +What: /sys/class/intel_pmt/telem/size +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + (RO) The size of telemetry region in bytes that corresponds to + the mapping size for the telem file. + +What: /sys/class/intel_pmt/telem/offset +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + (RO) The offset of telemetry region in bytes that corresponds to + the mapping for the telem file. diff --git a/MAINTAINERS b/MAINTAINERS index 8eb772814fb9..3d15a7b855cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9025,6 +9025,7 @@ INTEL PMT DRIVER M: "David E. Box" S: Maintained F: drivers/mfd/intel_pmt.c +F: drivers/platform/x86/intel_pmt_* INTEL PRO/WIRELESS 2100, 2200BG, 2915ABG NETWORK CONNECTION SUPPORT M: Stanislav Yakovlev diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0d91d136bc3b..7d168a434d05 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1362,6 +1362,18 @@ config INTEL_PMC_CORE - LTR Ignore - MPHY/PLL gating status (Sunrisepoint PCH only) +config INTEL_PMT_CLASS + tristate "Intel Platform Monitoring Technology (PMT) Class driver" + help + The Intel Platform Monitoring Technology (PMT) class driver provides + the basic sysfs interface and file hierarchy uses by PMT devices. + + For more information, see: + + + To compile this driver as a module, choose M here: the module + will be called intel_pmt_class. + config INTEL_PUNIT_IPC tristate "Intel P-Unit IPC Driver" help diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 5f823f7eff45..f4b1f87f2401 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -140,6 +140,7 @@ obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o +obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o diff --git a/drivers/platform/x86/intel_pmt_class.c b/drivers/platform/x86/intel_pmt_class.c new file mode 100644 index 000000000000..aa88dc23bbde --- /dev/null +++ b/drivers/platform/x86/intel_pmt_class.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitory Technology Telemetry driver + * + * Copyright (c) 2020, Intel Corporation. + * All Rights Reserved. + * + * Author: "Alexander Duyck" + */ + +#include +#include +#include +#include + +#include "intel_pmt_class.h" + +#define PMT_XA_START 0 +#define PMT_XA_MAX INT_MAX +#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX) + +/* + * sysfs + */ +static ssize_t +intel_pmt_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct intel_pmt_entry *entry = container_of(attr, + struct intel_pmt_entry, + pmt_bin_attr); + + if (off < 0) + return -EINVAL; + + if (off >= entry->size) + return 0; + + if (count > entry->size - off) + count = entry->size - off; + + memcpy_fromio(buf, entry->base + off, count); + + return count; +} + +static int +intel_pmt_mmap(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, struct vm_area_struct *vma) +{ + struct intel_pmt_entry *entry = container_of(attr, + struct intel_pmt_entry, + pmt_bin_attr); + unsigned long vsize = vma->vm_end - vma->vm_start; + struct device *dev = kobj_to_dev(kobj); + unsigned long phys = entry->base_addr; + unsigned long pfn = PFN_DOWN(phys); + unsigned long psize; + + if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE)) + return -EROFS; + + psize = (PFN_UP(entry->base_addr + entry->size) - pfn) * PAGE_SIZE; + if (vsize > psize) { + dev_err(dev, "Requested mmap size is too large\n"); + return -EINVAL; + } + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + if (io_remap_pfn_range(vma, vma->vm_start, pfn, + vsize, vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static ssize_t +guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + + return sprintf(buf, "0x%x\n", entry->guid); +} +static DEVICE_ATTR_RO(guid); + +static ssize_t size_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + + return sprintf(buf, "%zu\n", entry->size); +} +static DEVICE_ATTR_RO(size); + +static ssize_t +offset_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", offset_in_page(entry->base_addr)); +} +static DEVICE_ATTR_RO(offset); + +static struct attribute *intel_pmt_attrs[] = { + &dev_attr_guid.attr, + &dev_attr_size.attr, + &dev_attr_offset.attr, + NULL +}; +ATTRIBUTE_GROUPS(intel_pmt); + +static struct class intel_pmt_class = { + .name = "intel_pmt", + .owner = THIS_MODULE, + .dev_groups = intel_pmt_groups, +}; + +static int intel_pmt_populate_entry(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev, + struct resource *disc_res) +{ + struct pci_dev *pci_dev = to_pci_dev(dev->parent); + u8 bir; + + /* + * The base offset should always be 8 byte aligned. + * + * For non-local access types the lower 3 bits of base offset + * contains the index of the base address register where the + * telemetry can be found. + */ + bir = GET_BIR(header->base_offset); + + /* Local access and BARID only for now */ + switch (header->access_type) { + case ACCESS_LOCAL: + if (bir) { + dev_err(dev, + "Unsupported BAR index %d for access type %d\n", + bir, header->access_type); + return -EINVAL; + } + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + */ + entry->base_addr = disc_res->end + 1 + header->base_offset; + break; + case ACCESS_BARID: + /* + * If another BAR was specified then the base offset + * represents the offset within that BAR. SO retrieve the + * address from the parent PCI device and add offset. + */ + entry->base_addr = pci_resource_start(pci_dev, bir) + + GET_ADDRESS(header->base_offset); + break; + default: + dev_err(dev, "Unsupported access type %d\n", + header->access_type); + return -EINVAL; + } + + entry->guid = header->guid; + entry->size = header->size; + + return 0; +} + +static int intel_pmt_dev_register(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns, + struct device *parent) +{ + struct resource res; + struct device *dev; + int ret; + + ret = xa_alloc(ns->xa, &entry->devid, entry, PMT_XA_LIMIT, GFP_KERNEL); + if (ret) + return ret; + + dev = device_create(&intel_pmt_class, parent, MKDEV(0, 0), entry, + "%s%d", ns->name, entry->devid); + + if (IS_ERR(dev)) { + dev_err(parent, "Could not create %s%d device node\n", + ns->name, entry->devid); + ret = PTR_ERR(dev); + goto fail_dev_create; + } + + entry->kobj = &dev->kobj; + + if (ns->attr_grp) { + ret = sysfs_create_group(entry->kobj, ns->attr_grp); + if (ret) + goto fail_sysfs; + } + + /* if size is 0 assume no data buffer, so no file needed */ + if (!entry->size) + return 0; + + res.start = entry->base_addr; + res.end = res.start + entry->size - 1; + res.flags = IORESOURCE_MEM; + + entry->base = devm_ioremap_resource(dev, &res); + if (IS_ERR(entry->base)) { + ret = PTR_ERR(entry->base); + goto fail_ioremap; + } + + sysfs_bin_attr_init(&entry->pmt_bin_attr); + entry->pmt_bin_attr.attr.name = ns->name; + entry->pmt_bin_attr.attr.mode = 0440; + entry->pmt_bin_attr.mmap = intel_pmt_mmap; + entry->pmt_bin_attr.read = intel_pmt_read; + entry->pmt_bin_attr.size = entry->size; + + ret = sysfs_create_bin_file(&dev->kobj, &entry->pmt_bin_attr); + if (!ret) + return 0; + +fail_ioremap: + sysfs_remove_group(entry->kobj, ns->attr_grp); +fail_sysfs: + device_unregister(dev); +fail_dev_create: + xa_erase(ns->xa, entry->devid); + + return ret; +} + +int intel_pmt_dev_create(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns, + struct platform_device *pdev, int idx) +{ + struct intel_pmt_header header; + struct resource *disc_res; + int ret = -ENODEV; + + disc_res = platform_get_resource(pdev, IORESOURCE_MEM, idx); + if (!disc_res) + return ret; + + entry->disc_table = devm_platform_ioremap_resource(pdev, idx); + if (IS_ERR(entry->disc_table)) + return PTR_ERR(entry->disc_table); + + ret = ns->pmt_header_decode(entry, &header, &pdev->dev); + if (ret) + return ret; + + ret = intel_pmt_populate_entry(entry, &header, &pdev->dev, disc_res); + if (ret) + return ret; + + return intel_pmt_dev_register(entry, ns, &pdev->dev); + +} +EXPORT_SYMBOL_GPL(intel_pmt_dev_create); + +void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns) +{ + struct device *dev = kobj_to_dev(entry->kobj); + + if (entry->size) + sysfs_remove_bin_file(entry->kobj, &entry->pmt_bin_attr); + + if (ns->attr_grp) + sysfs_remove_group(entry->kobj, ns->attr_grp); + + device_unregister(dev); + xa_erase(ns->xa, entry->devid); +} +EXPORT_SYMBOL_GPL(intel_pmt_dev_destroy); + +static int __init pmt_class_init(void) +{ + return class_register(&intel_pmt_class); +} + +static void __exit pmt_class_exit(void) +{ + class_unregister(&intel_pmt_class); +} + +module_init(pmt_class_init); +module_exit(pmt_class_exit); + +MODULE_AUTHOR("Alexander Duyck "); +MODULE_DESCRIPTION("Intel PMT Class driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_pmt_class.h b/drivers/platform/x86/intel_pmt_class.h new file mode 100644 index 000000000000..de8f8139ba31 --- /dev/null +++ b/drivers/platform/x86/intel_pmt_class.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INTEL_PMT_CLASS_H +#define _INTEL_PMT_CLASS_H + +#include +#include +#include +#include +#include +#include + +/* PMT access types */ +#define ACCESS_BARID 2 +#define ACCESS_LOCAL 3 + +/* PMT discovery base address/offset register layout */ +#define GET_BIR(v) ((v) & GENMASK(2, 0)) +#define GET_ADDRESS(v) ((v) & GENMASK(31, 3)) + +struct intel_pmt_entry { + struct bin_attribute pmt_bin_attr; + struct kobject *kobj; + void __iomem *disc_table; + void __iomem *base; + unsigned long base_addr; + size_t size; + u32 guid; + int devid; +}; + +struct intel_pmt_header { + u32 base_offset; + u32 size; + u32 guid; + u8 access_type; +}; + +struct intel_pmt_namespace { + const char *name; + struct xarray *xa; + const struct attribute_group *attr_grp; + int (*pmt_header_decode)(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev); +}; + +int intel_pmt_dev_create(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns, + struct platform_device *pdev, int idx); +void intel_pmt_dev_destroy(struct intel_pmt_entry *entry, + struct intel_pmt_namespace *ns); +#endif -- cgit v1.2.3 From 5ef9998c96b0c99c49c202054586967e609286d2 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Wed, 28 Oct 2020 18:55:36 -0700 Subject: platform/x86: Intel PMT Crashlog capability driver Add support for the Intel Platform Monitoring Technology crashlog interface. This interface provides a few sysfs values to allow for controlling the crashlog telemetry interface as well as a character driver to allow for mapping the crashlog memory region so that it can be accessed after a crashlog has been recorded. This driver is meant to only support the server version of the crashlog which is identified as crash_type 1 with a version of zero. Currently no other types are supported. Signed-off-by: Alexander Duyck Signed-off-by: David E. Box Reviewed-by: Hans de Goede Signed-off-by: Lee Jones --- Documentation/ABI/testing/sysfs-class-intel_pmt | 65 +++++ drivers/platform/x86/Kconfig | 11 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/intel_pmt_crashlog.c | 328 ++++++++++++++++++++++++ 4 files changed, 405 insertions(+) create mode 100644 drivers/platform/x86/intel_pmt_crashlog.c (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-class-intel_pmt b/Documentation/ABI/testing/sysfs-class-intel_pmt index 926b5cf95fd1..ed4c886a21b1 100644 --- a/Documentation/ABI/testing/sysfs-class-intel_pmt +++ b/Documentation/ABI/testing/sysfs-class-intel_pmt @@ -52,3 +52,68 @@ Contact: David Box Description: (RO) The offset of telemetry region in bytes that corresponds to the mapping for the telem file. + +What: /sys/class/intel_pmt/crashlog +Date: October 2020 +KernelVersion: 5.10 +Contact: Alexander Duyck +Description: + The crashlog directory contains files for configuring an + instance of a PMT crashlog device that can perform crash data + recording. Each crashlog device has an associated crashlog + file. This file can be opened and mapped or read to access the + resulting crashlog buffer. The register layout for the buffer + can be determined from an XML file of specified GUID for the + parent device. + +What: /sys/class/intel_pmt/crashlog/crashlog +Date: October 2020 +KernelVersion: 5.10 +Contact: David Box +Description: + (RO) The crashlog buffer for this crashlog device. This file + may be mapped or read to obtain the data. + +What: /sys/class/intel_pmt/crashlog/guid +Date: October 2020 +KernelVersion: 5.10 +Contact: Alexander Duyck +Description: + (RO) The GUID for this crashlog device. The GUID identifies the + version of the XML file for the parent device that should be + used to determine the register layout. + +What: /sys/class/intel_pmt/crashlog/size +Date: October 2020 +KernelVersion: 5.10 +Contact: Alexander Duyck +Description: + (RO) The length of the result buffer in bytes that corresponds + to the size for the crashlog buffer. + +What: /sys/class/intel_pmt/crashlog/offset +Date: October 2020 +KernelVersion: 5.10 +Contact: Alexander Duyck +Description: + (RO) The offset of the buffer in bytes that corresponds + to the mapping for the crashlog device. + +What: /sys/class/intel_pmt/crashlog/enable +Date: October 2020 +KernelVersion: 5.10 +Contact: Alexander Duyck +Description: + (RW) Boolean value controlling if the crashlog functionality + is enabled for the crashlog device. + +What: /sys/class/intel_pmt/crashlog/trigger +Date: October 2020 +KernelVersion: 5.10 +Contact: Alexander Duyck +Description: + (RW) Boolean value controlling the triggering of the crashlog + device node. When read it provides data on if the crashlog has + been triggered. When written to it can be used to either clear + the current trigger by writing false, or to trigger a new + event if the trigger is not currently set. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6982c5671300..971d1cd06473 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1385,6 +1385,17 @@ config INTEL_PMT_TELEMETRY To compile this driver as a module, choose M here: the module will be called intel_pmt_telemetry. +config INTEL_PMT_CRASHLOG + tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver" + select INTEL_PMT_CLASS + help + The Intel Platform Monitoring Technology (PMT) crashlog driver provides + access to hardware crashlog capabilities on devices that support the + feature. + + To compile this driver as a module, choose M here: the module + will be called intel_pmt_crashlog. + config INTEL_PUNIT_IPC tristate "Intel P-Unit IPC Driver" help diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 6a7b61f59ea8..ca82c1344977 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -142,6 +142,7 @@ obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o obj-$(CONFIG_INTEL_PMT_TELEMETRY) += intel_pmt_telemetry.o +obj-$(CONFIG_INTEL_PMT_CRASHLOG) += intel_pmt_crashlog.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o diff --git a/drivers/platform/x86/intel_pmt_crashlog.c b/drivers/platform/x86/intel_pmt_crashlog.c new file mode 100644 index 000000000000..97dd749c8290 --- /dev/null +++ b/drivers/platform/x86/intel_pmt_crashlog.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Platform Monitoring Technology Crashlog driver + * + * Copyright (c) 2020, Intel Corporation. + * All Rights Reserved. + * + * Author: "Alexander Duyck" + */ + +#include +#include +#include +#include +#include +#include + +#include "intel_pmt_class.h" + +#define DRV_NAME "pmt_crashlog" + +/* Crashlog discovery header types */ +#define CRASH_TYPE_OOBMSM 1 + +/* Control Flags */ +#define CRASHLOG_FLAG_DISABLE BIT(27) + +/* + * Bits 28 and 29 control the state of bit 31. + * + * Bit 28 will clear bit 31, if set, allowing a new crashlog to be captured. + * Bit 29 will immediately trigger a crashlog to be generated, setting bit 31. + * Bit 30 is read-only and reserved as 0. + * Bit 31 is the read-only status with a 1 indicating log is complete. + */ +#define CRASHLOG_FLAG_TRIGGER_CLEAR BIT(28) +#define CRASHLOG_FLAG_TRIGGER_EXECUTE BIT(29) +#define CRASHLOG_FLAG_TRIGGER_COMPLETE BIT(31) +#define CRASHLOG_FLAG_TRIGGER_MASK GENMASK(31, 28) + +/* Crashlog Discovery Header */ +#define CONTROL_OFFSET 0x0 +#define GUID_OFFSET 0x4 +#define BASE_OFFSET 0x8 +#define SIZE_OFFSET 0xC +#define GET_ACCESS(v) ((v) & GENMASK(3, 0)) +#define GET_TYPE(v) (((v) & GENMASK(7, 4)) >> 4) +#define GET_VERSION(v) (((v) & GENMASK(19, 16)) >> 16) +/* size is in bytes */ +#define GET_SIZE(v) ((v) * sizeof(u32)) + +struct crashlog_entry { + /* entry must be first member of struct */ + struct intel_pmt_entry entry; + struct mutex control_mutex; +}; + +struct pmt_crashlog_priv { + int num_entries; + struct crashlog_entry entry[]; +}; + +/* + * I/O + */ +static bool pmt_crashlog_complete(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + /* return current value of the crashlog complete flag */ + return !!(control & CRASHLOG_FLAG_TRIGGER_COMPLETE); +} + +static bool pmt_crashlog_disabled(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + /* return current value of the crashlog disabled flag */ + return !!(control & CRASHLOG_FLAG_DISABLE); +} + +static bool pmt_crashlog_supported(struct intel_pmt_entry *entry) +{ + u32 discovery_header = readl(entry->disc_table + CONTROL_OFFSET); + u32 crash_type, version; + + crash_type = GET_TYPE(discovery_header); + version = GET_VERSION(discovery_header); + + /* + * Currently we only recognize OOBMSM version 0 devices. + * We can ignore all other crashlog devices in the system. + */ + return crash_type == CRASH_TYPE_OOBMSM && version == 0; +} + +static void pmt_crashlog_set_disable(struct intel_pmt_entry *entry, + bool disable) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + /* clear trigger bits so we are only modifying disable flag */ + control &= ~CRASHLOG_FLAG_TRIGGER_MASK; + + if (disable) + control |= CRASHLOG_FLAG_DISABLE; + else + control &= ~CRASHLOG_FLAG_DISABLE; + + writel(control, entry->disc_table + CONTROL_OFFSET); +} + +static void pmt_crashlog_set_clear(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + control &= ~CRASHLOG_FLAG_TRIGGER_MASK; + control |= CRASHLOG_FLAG_TRIGGER_CLEAR; + + writel(control, entry->disc_table + CONTROL_OFFSET); +} + +static void pmt_crashlog_set_execute(struct intel_pmt_entry *entry) +{ + u32 control = readl(entry->disc_table + CONTROL_OFFSET); + + control &= ~CRASHLOG_FLAG_TRIGGER_MASK; + control |= CRASHLOG_FLAG_TRIGGER_EXECUTE; + + writel(control, entry->disc_table + CONTROL_OFFSET); +} + +/* + * sysfs + */ +static ssize_t +enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry = dev_get_drvdata(dev); + int enabled = !pmt_crashlog_disabled(entry); + + return sprintf(buf, "%d\n", enabled); +} + +static ssize_t +enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct crashlog_entry *entry; + bool enabled; + int result; + + entry = dev_get_drvdata(dev); + + result = kstrtobool(buf, &enabled); + if (result) + return result; + + mutex_lock(&entry->control_mutex); + pmt_crashlog_set_disable(&entry->entry, !enabled); + mutex_unlock(&entry->control_mutex); + + return count; +} +static DEVICE_ATTR_RW(enable); + +static ssize_t +trigger_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct intel_pmt_entry *entry; + int trigger; + + entry = dev_get_drvdata(dev); + trigger = pmt_crashlog_complete(entry); + + return sprintf(buf, "%d\n", trigger); +} + +static ssize_t +trigger_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct crashlog_entry *entry; + bool trigger; + int result; + + entry = dev_get_drvdata(dev); + + result = kstrtobool(buf, &trigger); + if (result) + return result; + + mutex_lock(&entry->control_mutex); + + if (!trigger) { + pmt_crashlog_set_clear(&entry->entry); + } else if (pmt_crashlog_complete(&entry->entry)) { + /* we cannot trigger a new crash if one is still pending */ + result = -EEXIST; + goto err; + } else if (pmt_crashlog_disabled(&entry->entry)) { + /* if device is currently disabled, return busy */ + result = -EBUSY; + goto err; + } else { + pmt_crashlog_set_execute(&entry->entry); + } + + result = count; +err: + mutex_unlock(&entry->control_mutex); + return result; +} +static DEVICE_ATTR_RW(trigger); + +static struct attribute *pmt_crashlog_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_trigger.attr, + NULL +}; + +static struct attribute_group pmt_crashlog_group = { + .attrs = pmt_crashlog_attrs, +}; + +static int pmt_crashlog_header_decode(struct intel_pmt_entry *entry, + struct intel_pmt_header *header, + struct device *dev) +{ + void __iomem *disc_table = entry->disc_table; + struct crashlog_entry *crashlog; + + if (!pmt_crashlog_supported(entry)) + return 1; + + /* initialize control mutex */ + crashlog = container_of(entry, struct crashlog_entry, entry); + mutex_init(&crashlog->control_mutex); + + header->access_type = GET_ACCESS(readl(disc_table)); + header->guid = readl(disc_table + GUID_OFFSET); + header->base_offset = readl(disc_table + BASE_OFFSET); + + /* Size is measured in DWORDS, but accessor returns bytes */ + header->size = GET_SIZE(readl(disc_table + SIZE_OFFSET)); + + return 0; +} + +static DEFINE_XARRAY_ALLOC(crashlog_array); +static struct intel_pmt_namespace pmt_crashlog_ns = { + .name = "crashlog", + .xa = &crashlog_array, + .attr_grp = &pmt_crashlog_group, + .pmt_header_decode = pmt_crashlog_header_decode, +}; + +/* + * initialization + */ +static int pmt_crashlog_remove(struct platform_device *pdev) +{ + struct pmt_crashlog_priv *priv = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < priv->num_entries; i++) + intel_pmt_dev_destroy(&priv->entry[i].entry, &pmt_crashlog_ns); + + return 0; +} + +static int pmt_crashlog_probe(struct platform_device *pdev) +{ + struct pmt_crashlog_priv *priv; + size_t size; + int i, ret; + + size = struct_size(priv, entry, pdev->num_resources); + priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + for (i = 0; i < pdev->num_resources; i++) { + struct intel_pmt_entry *entry = &priv->entry[i].entry; + + ret = intel_pmt_dev_create(entry, &pmt_crashlog_ns, pdev, i); + if (ret < 0) + goto abort_probe; + if (ret) + continue; + + priv->num_entries++; + } + + return 0; +abort_probe: + pmt_crashlog_remove(pdev); + return ret; +} + +static struct platform_driver pmt_crashlog_driver = { + .driver = { + .name = DRV_NAME, + }, + .remove = pmt_crashlog_remove, + .probe = pmt_crashlog_probe, +}; + +static int __init pmt_crashlog_init(void) +{ + return platform_driver_register(&pmt_crashlog_driver); +} + +static void __exit pmt_crashlog_exit(void) +{ + platform_driver_unregister(&pmt_crashlog_driver); + xa_destroy(&crashlog_array); +} + +module_init(pmt_crashlog_init); +module_exit(pmt_crashlog_exit); + +MODULE_AUTHOR("Alexander Duyck "); +MODULE_DESCRIPTION("Intel PMT Crashlog driver"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3