linux-acpi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers
@ 2021-09-07 15:08 Chen Yu
  2021-09-07 15:15 ` [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation Chen Yu
                   ` (4 more replies)
  0 siblings, 5 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:08 UTC (permalink / raw)
  To: linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu

High Service Level Agreements (SLAs) requires that the system runs without
service interruptions. Generally, system firmware provides runtime services
such as RAS(Reliability, Availability and Serviceability) features, UEFI runtime
services and ACPI services. Currently if there is any firmware code changes in
these code area, the system firmware update and reboot is required. Example of
bug fix could be wrong register size or location of the register. This means
customer services are not available during the firmware upgrade, which could
approach several minutes, resulting in not able to meet SLAs.

Intel provides a mechanism named Management Mode Runtime Update to help the users
update the firmware without having to reboot[1].

This series provides the following facilities.

  1. Perform a runtime firmware driver update and activate.
  2. Ability to inject firmware code at runtime, for dynamic instrumentation.
  3. Facility to retrieve logs from runtime firmware update and activate telemetry.
     (The telemetry is based on runtime firmware update: it records the logs during
      runtime update(code injection and driver update).

The Management Mode Runtime Update OS Interface Specification[1] provides two ACPI
device objects to interface with system firmware to perform these updates. This patch
series introduces the drivers for those ACPI devices.

[1] https://uefi.org/sites/default/files/resources/Intel_MM_OS_Interface_Spec_Rev100.pdf

Chen Yu (5):
  Documentation: Introduce Platform Firmware Runtime Update
    documentation
  efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and
    corresponding structures
  drivers/acpi: Introduce Platform Firmware Runtime Update device driver
  drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry
  selftests/pfru: add test for Platform Firmware Runtime Update and
    Telemetry

 .../userspace-api/ioctl/ioctl-number.rst      |   1 +
 Documentation/x86/pfru.rst                    |  98 ++++
 drivers/acpi/Kconfig                          |   1 +
 drivers/acpi/Makefile                         |   1 +
 drivers/acpi/pfru/Kconfig                     |  29 +
 drivers/acpi/pfru/Makefile                    |   3 +
 drivers/acpi/pfru/pfru_telemetry.c            | 412 +++++++++++++
 drivers/acpi/pfru/pfru_update.c               | 544 ++++++++++++++++++
 include/linux/efi.h                           |  50 ++
 include/uapi/linux/pfru.h                     | 152 +++++
 tools/testing/selftests/Makefile              |   1 +
 tools/testing/selftests/pfru/Makefile         |   7 +
 tools/testing/selftests/pfru/config           |   2 +
 tools/testing/selftests/pfru/pfru.h           | 152 +++++
 tools/testing/selftests/pfru/pfru_test.c      | 324 +++++++++++
 15 files changed, 1777 insertions(+)
 create mode 100644 Documentation/x86/pfru.rst
 create mode 100644 drivers/acpi/pfru/Kconfig
 create mode 100644 drivers/acpi/pfru/Makefile
 create mode 100644 drivers/acpi/pfru/pfru_telemetry.c
 create mode 100644 drivers/acpi/pfru/pfru_update.c
 create mode 100644 include/uapi/linux/pfru.h
 create mode 100644 tools/testing/selftests/pfru/Makefile
 create mode 100644 tools/testing/selftests/pfru/config
 create mode 100644 tools/testing/selftests/pfru/pfru.h
 create mode 100644 tools/testing/selftests/pfru/pfru_test.c

-- 
2.25.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation
  2021-09-07 15:08 [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers Chen Yu
@ 2021-09-07 15:15 ` Chen Yu
  2021-09-07 15:23   ` Jonathan Corbet
  2021-09-07 15:17 ` [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures Chen Yu
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:15 UTC (permalink / raw)
  To: linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, H. Peter Anvin, Jonathan Corbet,
	linux-doc, x86

Add the Platform Firmware Runtime Update/Telemetry documentation.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
---
 Documentation/x86/pfru.rst | 98 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 98 insertions(+)
 create mode 100644 Documentation/x86/pfru.rst

diff --git a/Documentation/x86/pfru.rst b/Documentation/x86/pfru.rst
new file mode 100644
index 000000000000..321729f46737
--- /dev/null
+++ b/Documentation/x86/pfru.rst
@@ -0,0 +1,98 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================================================
+The Linux Platform Firmware Runtime Update and Telemetry
+========================================================
+
+According to the specification of <Management Mode Firmware Runtime Update>[1],
+certain computing systems require high Service Level Agreements (SLAs) where
+system reboot fewer firmware updates are required to deploy firmware changes
+to address bug fixes, security updates and to debug and root cause issues. This
+technology is called Intel Seamless Update. The management mode (MM),
+UEFI runtime services and ACPI services handle most of the system runtime
+functions. Changing the MM code execution during runtime is called MM Runtime
+Update. Since the "MM" acronyms might be misunderstood as "Memory Management",
+this driver uses "Platform Firmware Runtime Update"(PFRU)
+
+PFRU provides the following facilities: Performs a runtime firmware driver update
+and activate. Ability to inject firmware code at runtime, for dynamic instrumentation.
+PFRU Telemetry is a service which allows Runtime Update handler to produce telemetry
+data to upper layer OS consumer at runtime. The OS provides interfaces to let the
+users query the telemetry data via read operations. The specification specifies the
+interface and recommended policy to extract the data, the format and use are left to
+individual OEM's and BIOS implementations on what that data represents.
+
+PFRU interfaces
+=====================
+
+The user space tool manipulates on /dev/pfru/update for code injection and
+driver update. PFRU stands for Platform Firmware Runtime Update, and the /dev/pfru
+directory might be reserved for future usage.
+
+ 1. mmap the capsule file
+    fd_capsule = open("capsule.cap", O_RDONLY);
+    fstat(fd_capsule, &stat);
+    addr = mmap(0, stat.st_size, PROT_READ, fd_capsule);
+
+ 2. Get the capability information(version control, etc) from BIOS via
+    read() and do sanity check in user space.
+    fd_update = open("/dev/pfru/update", O_RDWR);
+    read(fd_update, &cap, sizeof(cap));
+    sanity_check(&cap);
+
+ 3. Write the capsule file to runtime update communication buffer
+    //kernel might return error if capsule file size is longer than
+    //communication buffer
+    write(fd_update, addr, stat.st_size);
+
+ 4. Stage the code injection
+    ioctl(fd_update, PFRU_IOC_STATGE);
+
+ 5. Activate the code injection
+    ioctl(fd_update, PFRU_IOC_ACTIVATE);
+
+ 6. Stage and activate the code injection
+    ioctl(fd_update, PFRU_IOC_STAGE_ACTIVATE);
+
+    PFRU_IOC_STATGE: Stage a capsule image from communication buffer
+                    and perform authentication.
+    PFRU_IOC_ACTIVATE: Activate a previous staged capsule image.
+    PFRU_IOC_STAGE_ACTIVATE: Perform both stage and activation actions.
+
+PFRU Telemetry
+=============
+
+The user space tool manipulates on /dev/pfru/telemetry for PFRU telemetry log.
+Sample code:
+
+ 1. Open telemetry device
+    fd_log = open("/dev/pfru/telemetry", O_RDWR);
+
+ 2. Get log level, log type, revision_id via one ioctl invoke
+    ioctl(fd_log, PFRU_IOC_GET_LOG_INFO, &info);
+
+ 3. Set log level, log type, revision_id
+    ioctl(fd_log, PFRU_IOC_SET_LOG_INFO, &info);
+
+ 4. ioctl(fd_log, PFRU_IOC_GET_DATA_INFO, &data_info);
+    Query the information of PFRU telemetry log buffer. The user is
+    responsible for parsing the result per the specification.
+
+ 5. Read the telemetry data:
+    read(fd_log, buf, data_info.size);
+
+Please refer to tools/testing/selftests/pfru/pfru_test.c for detail.
+
+According to <Management Mode Firmware Runtime Update>[1], the telemetry
+buffer is a wrap around buffer. If the telemetry buffer gets full, most recent
+log data will overwrite old log data. Besides, it is required in the spec that
+the read of telemetry should support both full data retrieval and delta telemetry
+data retrieval. Since this requirement is more likely a policy we leave this
+implementation in user space. That is to say, it is recommended for the user
+to double-read the telemetry parameters such as chunk1_size, chunk2_size,
+rollover_cnt in data_info structure to make sure that there is no more data appended
+while the user is reading the buffer. Besides, only after the runtime update has
+been run at least once, the telemetry log would have valid data, otherwise errno code
+of EBUSY would be returned.
+
+[1] https://uefi.org/sites/default/files/resources/Intel_MM_OS_Interface_Spec_Rev100.pdf
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures
  2021-09-07 15:08 [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers Chen Yu
  2021-09-07 15:15 ` [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation Chen Yu
@ 2021-09-07 15:17 ` Chen Yu
  2021-09-07 16:06   ` Ard Biesheuvel
  2021-09-07 15:27 ` [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver Chen Yu
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:17 UTC (permalink / raw)
  To: linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu, Ard Biesheuvel,
	linux-efi

Platform Firmware Runtime Update image starts with UEFI headers, and the headers
are defined in UEFI specification, but some of them have not been defined in the
kernel yet.

For example, the header layout of a capsule file looks like this:

EFI_CAPSULE_HEADER
EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER
EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER
EFI_FIRMWARE_IMAGE_AUTHENTICATION

These structures would be used by the Platform Firmware Runtime Update
driver to parse the format of capsule file to verify if the corresponding
version number is valid. The EFI_CAPSULE_HEADER has been defined in the
kernel, however the rest are not, thus introduce corresponding UEFI structures
accordingly.

The reason why efi_manage_capsule_header_t and efi_manage_capsule_image_header_t
are packedi might be that:
According to the uefi spec,
[Figure 23-6 Firmware Management and Firmware Image Management headers]
EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER is located at the lowest offset within
the body of the capsule. And this structure is designed to be unaligned to save
space, because in this way the adjacent drivers and binary payload elements could
start on byte boundary with no padding. And the
EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER is at the head of each payload, so
packing this structure also makes room for more data.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
---
 include/linux/efi.h | 50 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/include/linux/efi.h b/include/linux/efi.h
index 6b5d36babfcc..19ff834e1388 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -148,6 +148,56 @@ typedef struct {
 	u32 imagesize;
 } efi_capsule_header_t;
 
+#pragma pack(1)
+
+/* EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER */
+typedef struct {
+	u32	ver;
+	u16	emb_drv_cnt;
+	u16	payload_cnt;
+	/*
+	 * Variable array indicated by number of
+	 * (emb_drv_cnt + payload_cnt)
+	 */
+	u64	offset_list[];
+} efi_manage_capsule_header_t;
+
+/* EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER */
+typedef struct {
+	u32	ver;
+	guid_t	image_type_id;
+	u8	image_index;
+	u8	reserved_bytes[3];
+	u32	image_size;
+	u32	vendor_code_size;
+	/* ver = 2. */
+	u64	hw_ins;
+	/* ver = v3. */
+	u64	capsule_support;
+} efi_manage_capsule_image_header_t;
+
+#pragma pack()
+
+/* WIN_CERTIFICATE */
+typedef struct {
+	u32	len;
+	u16	rev;
+	u16	cert_type;
+} win_cert_t;
+
+/* WIN_CERTIFICATE_UEFI_GUID */
+typedef struct {
+	win_cert_t	hdr;
+	guid_t		cert_type;
+	u8		cert_data[];
+} win_cert_uefi_guid_t;
+
+/* EFI_FIRMWARE_IMAGE_AUTHENTICATIO */
+typedef struct {
+	u64				mon_count;
+	win_cert_uefi_guid_t		auth_info;
+} efi_image_auth_t;
+
 /*
  * EFI capsule flags
  */
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation
  2021-09-07 15:15 ` [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation Chen Yu
@ 2021-09-07 15:23   ` Jonathan Corbet
  2021-09-07 15:48     ` Chen Yu
  0 siblings, 1 reply; 16+ messages in thread
From: Jonathan Corbet @ 2021-09-07 15:23 UTC (permalink / raw)
  To: Chen Yu, linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, H. Peter Anvin, linux-doc, x86

Thanks for adding to the documentation.  I have a few nits for you...

Chen Yu <yu.c.chen@intel.com> writes:

> Add the Platform Firmware Runtime Update/Telemetry documentation.
>
> Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> ---
>  Documentation/x86/pfru.rst | 98 ++++++++++++++++++++++++++++++++++++++
>  1 file changed, 98 insertions(+)
>  create mode 100644 Documentation/x86/pfru.rst

When you add a new RST file, you also need to find a spot for it in
index.rst so it becomes part of the docs build.

> diff --git a/Documentation/x86/pfru.rst b/Documentation/x86/pfru.rst
> new file mode 100644
> index 000000000000..321729f46737
> --- /dev/null
> +++ b/Documentation/x86/pfru.rst
> @@ -0,0 +1,98 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +========================================================
> +The Linux Platform Firmware Runtime Update and Telemetry
> +========================================================
> +
> +According to the specification of <Management Mode Firmware Runtime Update>[1],
> +certain computing systems require high Service Level Agreements (SLAs) where
> +system reboot fewer firmware updates are required to deploy firmware changes
> +to address bug fixes, security updates and to debug and root cause issues. This
> +technology is called Intel Seamless Update. The management mode (MM),
> +UEFI runtime services and ACPI services handle most of the system runtime
> +functions. Changing the MM code execution during runtime is called MM Runtime
> +Update. Since the "MM" acronyms might be misunderstood as "Memory Management",
> +this driver uses "Platform Firmware Runtime Update"(PFRU)
> +
> +PFRU provides the following facilities: Performs a runtime firmware driver update
> +and activate. Ability to inject firmware code at runtime, for dynamic instrumentation.
> +PFRU Telemetry is a service which allows Runtime Update handler to produce telemetry
> +data to upper layer OS consumer at runtime. The OS provides interfaces to let the
> +users query the telemetry data via read operations. The specification specifies the
> +interface and recommended policy to extract the data, the format and use are left to
> +individual OEM's and BIOS implementations on what that data represents.

Sticking to the 80-column limit is preferable; it keeps the text
readable. 

> +PFRU interfaces
> +=====================

Underline lengths should match the title text, or Sphinx will get grumpy
with you.

> +The user space tool manipulates on /dev/pfru/update for code injection and
> +driver update. PFRU stands for Platform Firmware Runtime Update, and the /dev/pfru
> +directory might be reserved for future usage.
> +
> + 1. mmap the capsule file
> +    fd_capsule = open("capsule.cap", O_RDONLY);
> +    fstat(fd_capsule, &stat);
> +    addr = mmap(0, stat.st_size, PROT_READ, fd_capsule);

These will not render the way you would like; you'll want to use literal
blocks for the code samples.

> + 2. Get the capability information(version control, etc) from BIOS via
> +    read() and do sanity check in user space.
> +    fd_update = open("/dev/pfru/update", O_RDWR);
> +    read(fd_update, &cap, sizeof(cap));
> +    sanity_check(&cap);
> +
> + 3. Write the capsule file to runtime update communication buffer
> +    //kernel might return error if capsule file size is longer than
> +    //communication buffer
> +    write(fd_update, addr, stat.st_size);
> +
> + 4. Stage the code injection
> +    ioctl(fd_update, PFRU_IOC_STATGE);
> +
> + 5. Activate the code injection
> +    ioctl(fd_update, PFRU_IOC_ACTIVATE);
> +
> + 6. Stage and activate the code injection
> +    ioctl(fd_update, PFRU_IOC_STAGE_ACTIVATE);
> +
> +    PFRU_IOC_STATGE: Stage a capsule image from communication buffer
> +                    and perform authentication.
> +    PFRU_IOC_ACTIVATE: Activate a previous staged capsule image.
> +    PFRU_IOC_STAGE_ACTIVATE: Perform both stage and activation actions.
> +
> +PFRU Telemetry
> +=============
> +
> +The user space tool manipulates on /dev/pfru/telemetry for PFRU telemetry log.
> +Sample code:
> +
> + 1. Open telemetry device
> +    fd_log = open("/dev/pfru/telemetry", O_RDWR);
> +
> + 2. Get log level, log type, revision_id via one ioctl invoke
> +    ioctl(fd_log, PFRU_IOC_GET_LOG_INFO, &info);
> +
> + 3. Set log level, log type, revision_id
> +    ioctl(fd_log, PFRU_IOC_SET_LOG_INFO, &info);
> +
> + 4. ioctl(fd_log, PFRU_IOC_GET_DATA_INFO, &data_info);
> +    Query the information of PFRU telemetry log buffer. The user is
> +    responsible for parsing the result per the specification.
> +
> + 5. Read the telemetry data:
> +    read(fd_log, buf, data_info.size);
> +
> +Please refer to tools/testing/selftests/pfru/pfru_test.c for detail.
> +
> +According to <Management Mode Firmware Runtime Update>[1], the telemetry
> +buffer is a wrap around buffer. If the telemetry buffer gets full, most recent
> +log data will overwrite old log data. Besides, it is required in the spec that
> +the read of telemetry should support both full data retrieval and delta telemetry
> +data retrieval. Since this requirement is more likely a policy we leave this
> +implementation in user space. That is to say, it is recommended for the user
> +to double-read the telemetry parameters such as chunk1_size, chunk2_size,
> +rollover_cnt in data_info structure to make sure that there is no more data appended
> +while the user is reading the buffer. Besides, only after the runtime update has
> +been run at least once, the telemetry log would have valid data, otherwise errno code
> +of EBUSY would be returned.
> +
> +[1] https://uefi.org/sites/default/files/resources/Intel_MM_OS_Interface_Spec_Rev100.pdf
> -- 
> 2.25.1

Thanks,

jon

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver
  2021-09-07 15:08 [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers Chen Yu
  2021-09-07 15:15 ` [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation Chen Yu
  2021-09-07 15:17 ` [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures Chen Yu
@ 2021-09-07 15:27 ` Chen Yu
  2021-09-08  9:04   ` Mike Rapoport
  2021-09-07 15:39 ` [PATCH 4/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry Chen Yu
  2021-09-07 15:40 ` [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry Chen Yu
  4 siblings, 1 reply; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:27 UTC (permalink / raw)
  To: linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu, Jonathan Corbet,
	Greg Kroah-Hartman, Hans de Goede, Maximilian Luz,
	Alexander Graf, Jarkko Sakkinen, Shuo Liu, Hannes Reinecke,
	Ioana Ciornei, Jiri Slaby, Andra Paraschiv, Randy Dunlap,
	Ben Widawsky, Mike Rapoport, linux-doc

Introduce the pfru_update driver which can be used for Platform Firmware
Runtime code injection and driver update. The user is expected to provide
the update firmware in the form of capsule file, and pass it to the driver
via ioctl. Then the driver would hand this capsule file to the Platform
Firmware Runtime Update via the ACPI device _DSM method. At last the low
level Management Mode would do the firmware update.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
---
 .../userspace-api/ioctl/ioctl-number.rst      |   1 +
 drivers/acpi/Kconfig                          |   1 +
 drivers/acpi/Makefile                         |   1 +
 drivers/acpi/pfru/Kconfig                     |  15 +
 drivers/acpi/pfru/Makefile                    |   2 +
 drivers/acpi/pfru/pfru_update.c               | 544 ++++++++++++++++++
 include/uapi/linux/pfru.h                     | 106 ++++
 7 files changed, 670 insertions(+)
 create mode 100644 drivers/acpi/pfru/Kconfig
 create mode 100644 drivers/acpi/pfru/Makefile
 create mode 100644 drivers/acpi/pfru/pfru_update.c
 create mode 100644 include/uapi/linux/pfru.h

diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 1409e40e6345..976920c404dc 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -365,6 +365,7 @@ Code  Seq#    Include File                                           Comments
                                                                      <mailto:aherrman@de.ibm.com>
 0xE5  00-3F  linux/fuse.h
 0xEC  00-01  drivers/platform/chrome/cros_ec_dev.h                   ChromeOS EC driver
+0xEE  00-1F  uapi/linux/pfru.h                                       Platform Firmware Runtime Update and Telemetry
 0xF3  00-3F  drivers/usb/misc/sisusbvga/sisusb.h                     sisfb (in development)
                                                                      <mailto:thomas@winischhofer.net>
 0xF6  all                                                            LTTng Linux Trace Toolkit Next Generation
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 8f9940f40baa..e7f721bed20d 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -482,6 +482,7 @@ source "drivers/acpi/nfit/Kconfig"
 source "drivers/acpi/numa/Kconfig"
 source "drivers/acpi/apei/Kconfig"
 source "drivers/acpi/dptf/Kconfig"
+source "drivers/acpi/pfru/Kconfig"
 
 config ACPI_WATCHDOG
 	bool
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 3018714e87d9..9c2c5ddff6ec 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -102,6 +102,7 @@ obj-$(CONFIG_ACPI_CPPC_LIB)	+= cppc_acpi.o
 obj-$(CONFIG_ACPI_SPCR_TABLE)	+= spcr.o
 obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o
 obj-$(CONFIG_ACPI_PPTT) 	+= pptt.o
+obj-$(CONFIG_ACPI_PFRU)		+= pfru/
 
 # processor has its own "processor." module_param namespace
 processor-y			:= processor_driver.o
diff --git a/drivers/acpi/pfru/Kconfig b/drivers/acpi/pfru/Kconfig
new file mode 100644
index 000000000000..3f31b7d95f3b
--- /dev/null
+++ b/drivers/acpi/pfru/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+config ACPI_PFRU
+	tristate "ACPI Platform Firmware Runtime Update (PFRU)"
+	depends on 64BIT
+	help
+	  In order to reduce the system reboot times and update the platform firmware
+	  in time, Platform Firmware Runtime Update is leveraged to patch the system
+	  without reboot. This driver supports Platform Firmware Runtime Update,
+	  which is composed of two parts: code injection and driver update.
+
+	  For more information, see:
+	  <file:Documentation/x86/pfru_update.rst>
+
+	  To compile this driver as a module, choose M here:
+	  the module will be called pfru_update.
diff --git a/drivers/acpi/pfru/Makefile b/drivers/acpi/pfru/Makefile
new file mode 100644
index 000000000000..098cbe80cf3d
--- /dev/null
+++ b/drivers/acpi/pfru/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ACPI_PFRU) += pfru_update.o
diff --git a/drivers/acpi/pfru/pfru_update.c b/drivers/acpi/pfru/pfru_update.c
new file mode 100644
index 000000000000..3ddf42e6d355
--- /dev/null
+++ b/drivers/acpi/pfru/pfru_update.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI Platform Firmware Runtime Update Device Driver
+ *
+ * Copyright (C) 2021 Intel Corporation
+ * Author: Chen Yu <yu.c.chen@intel.com>
+ */
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/efi.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/uuid.h>
+#include <uapi/linux/pfru.h>
+
+struct pfru_device {
+	guid_t uuid;
+	int rev_id;
+	struct device *dev;
+};
+
+static struct pfru_device pfru_dev;
+static struct pfru_device *get_pfru_dev(void)
+{
+	return &pfru_dev;
+}
+
+static int query_capability(struct update_cap_info *cap)
+{
+	union acpi_object *out_obj;
+	struct pfru_device *pf_dev;
+	acpi_handle handle;
+	int i, ret = -EINVAL;
+
+	pf_dev = get_pfru_dev();
+	handle = ACPI_HANDLE(pf_dev->dev);
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pf_dev->uuid,
+					  pf_dev->rev_id, FUNC_QUERY_UPDATE_CAP,
+					  NULL, ACPI_TYPE_PACKAGE);
+	if (!out_obj)
+		return -EINVAL;
+
+	for (i = 0; i < out_obj->package.count; i++) {
+		union acpi_object *obj = &out_obj->package.elements[i];
+
+		switch (i) {
+		case 0:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			cap->status = obj->integer.value;
+			break;
+		case 1:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			cap->update_cap = obj->integer.value;
+			break;
+		case 2:
+			if (obj->type != ACPI_TYPE_BUFFER)
+				goto free_acpi_buffer;
+			memcpy(&cap->code_type, obj->buffer.pointer,
+			       obj->buffer.length);
+			break;
+		case 3:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			cap->fw_version = obj->integer.value;
+			break;
+		case 4:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			cap->code_rt_version = obj->integer.value;
+			break;
+		case 5:
+			if (obj->type != ACPI_TYPE_BUFFER)
+				goto free_acpi_buffer;
+			memcpy(&cap->drv_type, obj->buffer.pointer,
+			       obj->buffer.length);
+			break;
+		case 6:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			cap->drv_rt_version = obj->integer.value;
+			break;
+		case 7:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			cap->drv_svn = obj->integer.value;
+			break;
+		case 8:
+			if (obj->type != ACPI_TYPE_BUFFER)
+				goto free_acpi_buffer;
+			memcpy(&cap->platform_id, obj->buffer.pointer,
+			       obj->buffer.length);
+			break;
+		case 9:
+			if (obj->type != ACPI_TYPE_BUFFER)
+				goto free_acpi_buffer;
+			memcpy(&cap->oem_id, obj->buffer.pointer,
+			       obj->buffer.length);
+			break;
+		}
+	}
+	ret = 0;
+
+free_acpi_buffer:
+	ACPI_FREE(out_obj);
+
+	return ret;
+}
+
+static int query_buffer(struct com_buf_info *info)
+{
+	union acpi_object *out_obj;
+	struct pfru_device *pf_dev;
+	acpi_handle handle;
+	int i, ret = -EINVAL;
+
+	pf_dev = get_pfru_dev();
+	handle = ACPI_HANDLE(pf_dev->dev);
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pf_dev->uuid,
+					  pf_dev->rev_id, FUNC_QUERY_BUF,
+					  NULL, ACPI_TYPE_PACKAGE);
+	if (!out_obj)
+		return -EINVAL;
+
+	for (i = 0; i < out_obj->package.count; i++) {
+		union acpi_object *obj = &out_obj->package.elements[i];
+
+		switch (i) {
+		case 0:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			info->status = obj->integer.value;
+			break;
+		case 1:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			info->ext_status = obj->integer.value;
+			break;
+		case 2:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			info->addr_lo = obj->integer.value;
+			break;
+		case 3:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			info->addr_hi = obj->integer.value;
+			break;
+		case 4:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			info->buf_size = obj->integer.value;
+			break;
+		}
+	}
+	ret = 0;
+
+free_acpi_buffer:
+	ACPI_FREE(out_obj);
+
+	return ret;
+}
+
+static int get_image_type(efi_manage_capsule_image_header_t *img_hdr,
+			  int *type)
+{
+	int ret;
+	guid_t code_inj_id, drv_update_id, *image_type_id;
+
+	ret = guid_parse(PFRU_CODE_INJ_UUID, &code_inj_id);
+	if (ret)
+		return ret;
+	ret = guid_parse(PFRU_DRV_UPDATE_UUID, &drv_update_id);
+	if (ret)
+		return ret;
+	/* check whether this is a code injection or driver update */
+	image_type_id = &img_hdr->image_type_id;
+	if (guid_equal(image_type_id, &code_inj_id))
+		*type = CODE_INJECT_TYPE;
+	else if (guid_equal(image_type_id, &drv_update_id))
+		*type = DRIVER_UPDATE_TYPE;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+/*
+ * The (u64 hw_ins) was introduced in UEFI spec version 2,
+ * and (u64 capsule_support) was introduced in version 3.
+ * The size needs to be adjusted accordingly.
+ */
+static int adjust_efi_size(efi_manage_capsule_image_header_t *img_hdr,
+			   int *size)
+{
+	int tmp_size = *size;
+
+	tmp_size += sizeof(efi_manage_capsule_image_header_t);
+	switch (img_hdr->ver) {
+	case 1:
+		tmp_size -= 2 * sizeof(u64);
+		break;
+	case 2:
+		tmp_size -= sizeof(u64);
+		break;
+	default:
+		/* only support version 1 and 2 */
+		return -EINVAL;
+	}
+	*size = tmp_size;
+
+	return 0;
+}
+
+/*
+ * Sanity check if the capsule image has a newer version than current one.
+ * Return: true if it is valid, false otherwise.
+ */
+static bool valid_version(const void *data, struct update_cap_info *cap)
+{
+	struct payload_hdr *payload_hdr;
+	efi_capsule_header_t *cap_hdr;
+	efi_manage_capsule_header_t *m_hdr;
+	efi_manage_capsule_image_header_t *m_img_hdr;
+	efi_image_auth_t *auth;
+	int type, size, ret;
+
+	cap_hdr = (efi_capsule_header_t *)data;
+	size = cap_hdr->headersize;
+	m_hdr = (efi_manage_capsule_header_t *)(data + size);
+	/*
+	 * Current data structure size plus variable array indicated
+	 * by number of (emb_drv_cnt + payload_cnt)
+	 */
+	size += sizeof(efi_manage_capsule_header_t) +
+		      (m_hdr->emb_drv_cnt + m_hdr->payload_cnt) * sizeof(u64);
+	m_img_hdr = (efi_manage_capsule_image_header_t *)(data + size);
+
+	ret = get_image_type(m_img_hdr, &type);
+	if (ret)
+		return false;
+
+	ret = adjust_efi_size(m_img_hdr, &size);
+	if (ret)
+		return false;
+
+	auth = (efi_image_auth_t *)(data + size);
+	size += sizeof(u64) + auth->auth_info.hdr.len;
+	payload_hdr = (struct payload_hdr *)(data + size);
+
+	/* Finally, compare the version. */
+	if (type == CODE_INJECT_TYPE)
+		return payload_hdr->rt_ver >= cap->code_rt_version;
+	else
+		return payload_hdr->rt_ver >= cap->drv_rt_version;
+}
+
+static void parse_update_result(struct updated_result *result)
+{
+	pr_debug("Update result:\n");
+	pr_debug("Status:%d\n", result->status);
+	pr_debug("Extended Status:%d\n", result->ext_status);
+	pr_debug("Authentication Time Low:%ld\n", result->low_auth_time);
+	pr_debug("Authentication Time High:%ld\n", result->high_auth_time);
+	pr_debug("Execution Time Low:%ld\n", result->low_exec_time);
+	pr_debug("Execution Time High:%ld\n", result->high_exec_time);
+}
+
+static int start_acpi_update(int action)
+{
+	union acpi_object *out_obj, in_obj, in_buf;
+	struct updated_result update_result;
+	acpi_handle handle;
+	struct pfru_device *pf_dev;
+	int i, ret = -EINVAL;
+
+	pf_dev = get_pfru_dev();
+	memset(&in_obj, 0, sizeof(in_obj));
+	memset(&in_buf, 0, sizeof(in_buf));
+	in_obj.type = ACPI_TYPE_PACKAGE;
+	in_obj.package.count = 1;
+	in_obj.package.elements = &in_buf;
+	in_buf.type = ACPI_TYPE_INTEGER;
+	in_buf.integer.value = action;
+
+	handle = ACPI_HANDLE(pf_dev->dev);
+	out_obj = acpi_evaluate_dsm_typed(handle, &pf_dev->uuid,
+					  pf_dev->rev_id, FUNC_START,
+					  &in_obj, ACPI_TYPE_PACKAGE);
+	if (!out_obj)
+		return -EINVAL;
+
+	for (i = 0; i < out_obj->package.count; i++) {
+		union acpi_object *obj = &out_obj->package.elements[i];
+
+		switch (i) {
+		case 0:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			update_result.status = obj->integer.value;
+			break;
+		case 1:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			update_result.ext_status = obj->integer.value;
+			break;
+		case 2:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			update_result.low_auth_time = obj->integer.value;
+			break;
+		case 3:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			update_result.high_auth_time = obj->integer.value;
+			break;
+		case 4:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			update_result.low_exec_time = obj->integer.value;
+			break;
+		case 5:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			update_result.high_exec_time = obj->integer.value;
+			break;
+		}
+	}
+	parse_update_result(&update_result);
+	ret = 0;
+
+free_acpi_buffer:
+	ACPI_FREE(out_obj);
+
+	return ret;
+}
+
+static long pfru_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *p;
+	int ret = 0, rev;
+	struct pfru_device *pf_dev;
+
+	pf_dev = get_pfru_dev();
+
+	p = (void __user *)arg;
+
+	switch (cmd) {
+	case PFRU_IOC_SET_REV:
+		if (copy_from_user(&rev, p, sizeof(unsigned int)))
+			return -EFAULT;
+		if (!valid_revid(rev))
+			return -EFAULT;
+		pf_dev->rev_id = rev;
+		break;
+	case PFRU_IOC_STAGE:
+		ret = start_acpi_update(START_STAGE);
+		if (ret)
+			return ret;
+		break;
+	case PFRU_IOC_ACTIVATE:
+		ret = start_acpi_update(START_ACTIVATE);
+		if (ret)
+			return ret;
+		break;
+	case PFRU_IOC_STAGE_ACTIVATE:
+		ret = start_acpi_update(START_STAGE_ACTIVATE);
+		if (ret)
+			return ret;
+		break;
+	default:
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static long compat_pfru_ioctl(struct file *filep, unsigned int cmd,
+			      unsigned long arg)
+{
+	return pfru_ioctl(filep, cmd, arg);
+}
+#endif
+
+static int pfru_open(struct inode *inode, struct file *file)
+{
+	return capable(CAP_SYS_RAWIO) ? stream_open(inode, file) : -EPERM;
+}
+
+static ssize_t pfru_write(struct file *file, const char __user *buf,
+			  size_t len, loff_t *ppos)
+{
+	struct update_cap_info cap;
+	struct com_buf_info info;
+	phys_addr_t phy_addr;
+	struct iov_iter iter;
+	struct iovec iov;
+	char *buf_ptr;
+	int ret;
+
+	ret = query_buffer(&info);
+	if (ret)
+		return ret;
+
+	if (len > info.buf_size)
+		return -EINVAL;
+
+	iov.iov_base = (void __user *)buf;
+	iov.iov_len = len;
+	iov_iter_init(&iter, WRITE, &iov, 1, len);
+
+	/* map the communication buffer */
+	phy_addr = (phys_addr_t)(info.addr_lo | (info.addr_hi << 32));
+	buf_ptr = memremap(phy_addr, info.buf_size, MEMREMAP_WB);
+	if (IS_ERR(buf_ptr))
+		return PTR_ERR(buf_ptr);
+	if (!copy_from_iter_full(buf_ptr, len, &iter)) {
+		pr_err("error! could not read capsule file\n");
+		ret = -EINVAL;
+		goto unmap;
+	}
+
+	/* Check if the capsule header has a valid version number. */
+	ret = query_capability(&cap);
+	if (ret)
+		goto unmap;
+
+	if (cap.status != DSM_SUCCEED) {
+		ret = -EBUSY;
+		goto unmap;
+	}
+	if (!valid_version(buf_ptr, &cap)) {
+		ret = -EINVAL;
+		goto unmap;
+	}
+	ret = 0;
+unmap:
+	memunmap(buf_ptr);
+
+	return ret ?: len;
+}
+
+static ssize_t pfru_read(struct file *filp, char __user *ubuf,
+			 size_t size, loff_t *off)
+{
+	struct update_cap_info cap;
+	int ret;
+
+	ret = query_capability(&cap);
+	if (ret)
+		return ret;
+
+	size = min_t(size_t, size, sizeof(cap));
+
+	if (copy_to_user(ubuf, &cap, size))
+		return -EFAULT;
+
+	return size;
+}
+
+static const struct file_operations acpi_pfru_fops = {
+	.owner		= THIS_MODULE,
+	.write		= pfru_write,
+	.read		= pfru_read,
+	.open		= pfru_open,
+	.unlocked_ioctl = pfru_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= compat_pfru_ioctl,
+#endif
+	.llseek		= noop_llseek,
+};
+
+static struct miscdevice pfru_misc_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "pfru_update",
+	.nodename = "pfru/update",
+	.fops = &acpi_pfru_fops,
+};
+
+static int acpi_pfru_remove(struct platform_device *pdev)
+{
+	misc_deregister(&pfru_misc_dev);
+
+	return 0;
+}
+
+static int acpi_pfru_probe(struct platform_device *pdev)
+{
+	acpi_handle handle;
+	struct pfru_device *pf_dev;
+	int ret;
+
+	pf_dev = get_pfru_dev();
+
+	ret = guid_parse(PFRU_UUID, &pf_dev->uuid);
+	if (ret)
+		return ret;
+	/* default rev id is 1 */
+	pf_dev->rev_id = 1;
+	pf_dev->dev = &pdev->dev;
+	handle = ACPI_HANDLE(pf_dev->dev);
+	if (!acpi_has_method(handle, "_DSM")) {
+		pr_err("Missing _DSM\n");
+		return -ENODEV;
+	}
+
+	ret = misc_register(&pfru_misc_dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct acpi_device_id acpi_pfru_ids[] = {
+	{"INTC1080", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, acpi_pfru_ids);
+
+static struct platform_driver acpi_pfru_driver = {
+	.driver = {
+		.name = "pfru_update",
+		.acpi_match_table = acpi_pfru_ids,
+	},
+	.probe = acpi_pfru_probe,
+	.remove = acpi_pfru_remove,
+};
+module_platform_driver(acpi_pfru_driver);
+
+MODULE_DESCRIPTION("Platform Firmware Runtime Update device driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/pfru.h b/include/uapi/linux/pfru.h
new file mode 100644
index 000000000000..81eb8ad5a57e
--- /dev/null
+++ b/include/uapi/linux/pfru.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Platform Firmware Runtime Update header
+ *
+ * Copyright(c) 2021 Intel Corporation. All rights reserved.
+ */
+#ifndef __PFRU_H__
+#define __PFRU_H__
+
+#include <linux/ioctl.h>
+#include <linux/uuid.h>
+
+#define PFRU_UUID		"ECF9533B-4A3C-4E89-939E-C77112601C6D"
+#define PFRU_CODE_INJ_UUID		"B2F84B79-7B6E-4E45-885F-3FB9BB185402"
+#define PFRU_DRV_UPDATE_UUID		"4569DD8C-75F1-429A-A3D6-24DE8097A0DF"
+
+#define FUNC_STANDARD_QUERY	0
+#define FUNC_QUERY_UPDATE_CAP	1
+#define FUNC_QUERY_BUF		2
+#define FUNC_START		3
+
+#define CODE_INJECT_TYPE	1
+#define DRIVER_UPDATE_TYPE	2
+
+#define REVID_1		1
+#define REVID_2		2
+
+#define PFRU_MAGIC 0xEE
+
+#define PFRU_IOC_SET_REV _IOW(PFRU_MAGIC, 0x01, unsigned int)
+#define PFRU_IOC_STAGE _IOW(PFRU_MAGIC, 0x02, unsigned int)
+#define PFRU_IOC_ACTIVATE _IOW(PFRU_MAGIC, 0x03, unsigned int)
+#define PFRU_IOC_STAGE_ACTIVATE _IOW(PFRU_MAGIC, 0x04, unsigned int)
+
+static inline int valid_revid(int id)
+{
+	return (id == REVID_1) || (id == REVID_2);
+}
+
+/* Capsule file payload header */
+struct payload_hdr {
+	__u32	sig;
+	__u32	hdr_version;
+	__u32	hdr_size;
+	__u32	hw_ver;
+	__u32	rt_ver;
+	guid_t	platform_id;
+};
+
+enum start_action {
+	START_STAGE,
+	START_ACTIVATE,
+	START_STAGE_ACTIVATE,
+};
+
+enum dsm_status {
+	DSM_SUCCEED,
+	DSM_FUNC_NOT_SUPPORT,
+	DSM_INVAL_INPUT,
+	DSM_HARDWARE_ERR,
+	DSM_RETRY_SUGGESTED,
+	DSM_UNKNOWN,
+	DSM_FUNC_SPEC_ERR,
+};
+
+struct update_cap_info {
+	enum dsm_status status;
+	int update_cap;
+
+	guid_t code_type;
+	int fw_version;
+	int code_rt_version;
+
+	guid_t drv_type;
+	int drv_rt_version;
+	int drv_svn;
+
+	guid_t platform_id;
+	guid_t oem_id;
+
+	char oem_info[];
+};
+
+struct com_buf_info {
+	enum dsm_status status;
+	enum dsm_status ext_status;
+	unsigned long addr_lo;
+	unsigned long addr_hi;
+	int buf_size;
+};
+
+struct capsulate_buf_info {
+	unsigned long src;
+	int size;
+};
+
+struct updated_result {
+	enum dsm_status status;
+	enum dsm_status ext_status;
+	unsigned long low_auth_time;
+	unsigned long high_auth_time;
+	unsigned long low_exec_time;
+	unsigned long high_exec_time;
+};
+
+#endif /* __PFRU_H__ */
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 4/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry
  2021-09-07 15:08 [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers Chen Yu
                   ` (2 preceding siblings ...)
  2021-09-07 15:27 ` [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver Chen Yu
@ 2021-09-07 15:39 ` Chen Yu
  2021-09-07 15:40 ` [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry Chen Yu
  4 siblings, 0 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:39 UTC (permalink / raw)
  To: linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu

Platform Firmware Runtime Update(PFRU) Telemetry Service is part of RoT
(Root of Trust), which allows PFRU handler and other PFRU drivers to produce
telemetry data to upper layer OS consumer at runtime.

The linux provides interfaces for the user to query the parameters of
telemetry data, and the user could read out the telemetry data accordingly.

Typical log looks like:

[SmmRuntimeUpdateHandler.ProcessSmmRuntimeUpdate] ProcessSmmRuntimeUpdate = START, Action = 2
[SmmRuntimeUpdateHandler.ProcessSmmRuntimeUpdate] FwVersion = 0, CodeInjectionVersion = 1
[ShadowSmmRuntimeUpdateImage] Image = 0x74D9B000, ImageSize = 0x1172
[ProcessSmmRuntimeUpdate] ShadowSmmRuntimeUpdateImage.Status = Success
[ValidateSmmRuntimeUpdateImage] CapsuleHeader.CapsuleGuid = 6DCBD5ED-E82D-4C44-BDA1-7194199AD92A
[ValidateSmmRuntimeUpdateImage] FmpCapHeader.Version = 1
[ValidateSmmRuntimeUpdateImage] FmpCapImageHeader.UpdateImageTypeId = B2F84B79-7B6E-4E45-885F-3FB9BB185402
[ValidateSmmRuntimeUpdateImage] SmmRuntimeUpdateVerifyImageWithDenylist.Status = Success
[ValidateSmmRuntimeUpdateImage] SmmRuntimeUpdateVerifyImageWithAllowlist.Status = Success
[SmmCodeInjectionVerifyPayloadHeader] PayloadHeader.Signature = 0x31494353
[SmmCodeInjectionVerifyPayloadHeader] PayloadHeader.PlatformId = 63462139-A8B1-AA4E-9024-F2BB53EA4723
[SmmCodeInjectionVerifyPayloadHeader] PayloadHeader.SupportedSmmFirmwareVersion = 0, PayloadHeader.SmmCodeInjectionRuntimeVersion = 1
[ProcessSmmRuntimeUpdate] ValidateSmmRuntimeUpdateImage.Status = Success
CPU CSR[0B102D28] Before = 7FBF830E
CPU CSR[0B102D28] After = 7FBF8310
[ProcessSmmRuntimeUpdate] ProcessSmmCodeInjection.Status = Success
[SmmRuntimeUpdateHandler.ProcessSmmRuntimeUpdate] ProcessSmmRuntimeUpdate = End, Status = Success

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
---
 drivers/acpi/pfru/Kconfig          |  14 +
 drivers/acpi/pfru/Makefile         |   1 +
 drivers/acpi/pfru/pfru_telemetry.c | 412 +++++++++++++++++++++++++++++
 include/uapi/linux/pfru.h          |  46 ++++
 4 files changed, 473 insertions(+)
 create mode 100644 drivers/acpi/pfru/pfru_telemetry.c

diff --git a/drivers/acpi/pfru/Kconfig b/drivers/acpi/pfru/Kconfig
index 3f31b7d95f3b..e2934058884e 100644
--- a/drivers/acpi/pfru/Kconfig
+++ b/drivers/acpi/pfru/Kconfig
@@ -13,3 +13,17 @@ config ACPI_PFRU
 
 	  To compile this driver as a module, choose M here:
 	  the module will be called pfru_update.
+
+config ACPI_PFRU_TELEMETRY
+	tristate "ACPI Platform Firmware Runtime Update Telemetry Service"
+	depends on ACPI_PFRU
+	help
+	  PFRU(Platform Firmware Runtime Update) Telemetry Service is part of
+	  RoT(Root of Trust), which allows Platform Firmware Runtime Update handler
+	  and other PFRU drivers to produce telemetry data to upper layer OS consumer
+	  at runtime.
+
+	  For more information, see:
+	  <file:Documentation/x86/pfru_update.rst>
+
+	  If unsure, please say N.
diff --git a/drivers/acpi/pfru/Makefile b/drivers/acpi/pfru/Makefile
index 098cbe80cf3d..30060ba320ea 100644
--- a/drivers/acpi/pfru/Makefile
+++ b/drivers/acpi/pfru/Makefile
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_ACPI_PFRU) += pfru_update.o
+obj-$(CONFIG_ACPI_PFRU_TELEMETRY) += pfru_telemetry.o
diff --git a/drivers/acpi/pfru/pfru_telemetry.c b/drivers/acpi/pfru/pfru_telemetry.c
new file mode 100644
index 000000000000..0873eb8bb69e
--- /dev/null
+++ b/drivers/acpi/pfru/pfru_telemetry.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI Platform Firmware Runtime Update
+ * Telemetry Service Device Driver
+ *
+ * Copyright (C) 2021 Intel Corporation
+ * Author: Chen Yu <yu.c.chen@intel.com>
+ */
+#include <linux/acpi.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/minmax.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/platform_device.h>
+#include <linux/uuid.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/pfru.h>
+
+struct pfru_telem_device {
+	struct device *dev;
+	guid_t uuid;
+	struct telem_info info;
+};
+
+static struct pfru_telem_device telem_dev;
+static struct pfru_telem_device *get_pfru_telem_dev(void)
+{
+	return &telem_dev;
+}
+
+static int get_pfru_data_info(struct telem_data_info *data_info,
+			      int log_type)
+{
+	struct pfru_telem_device *pf_telem_dev;
+	union acpi_object *out_obj, in_obj, in_buf;
+	acpi_handle handle;
+	int i, ret = -EINVAL;
+
+	pf_telem_dev = get_pfru_telem_dev();
+	handle = ACPI_HANDLE(pf_telem_dev->dev);
+
+	memset(&in_obj, 0, sizeof(in_obj));
+	memset(&in_buf, 0, sizeof(in_buf));
+	in_obj.type = ACPI_TYPE_PACKAGE;
+	in_obj.package.count = 1;
+	in_obj.package.elements = &in_buf;
+	in_buf.type = ACPI_TYPE_INTEGER;
+	in_buf.integer.value = log_type;
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pf_telem_dev->uuid,
+					  pf_telem_dev->info.log_revid, FUNC_GET_DATA,
+					  &in_obj, ACPI_TYPE_PACKAGE);
+	if (!out_obj) {
+		pr_err("Failed to invoke _DSM\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < out_obj->package.count; i++) {
+		union acpi_object *obj = &out_obj->package.elements[i];
+
+		switch (i) {
+		case 0:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->status = obj->integer.value;
+			break;
+		case 1:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->ext_status = obj->integer.value;
+			break;
+		case 2:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->max_data_size = obj->integer.value;
+			break;
+		case 3:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->chunk1_addr_lo = obj->integer.value;
+			break;
+		case 4:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->chunk1_addr_hi = obj->integer.value;
+			break;
+		case 5:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->chunk1_size = obj->integer.value;
+			break;
+		case 6:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->chunk2_addr_lo = obj->integer.value;
+			break;
+		case 7:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->chunk2_addr_hi = obj->integer.value;
+			break;
+		case 8:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->chunk2_size = obj->integer.value;
+			break;
+		case 9:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->rollover_cnt = obj->integer.value;
+			break;
+		case 10:
+			if (obj->type != ACPI_TYPE_INTEGER)
+				goto free_acpi_buffer;
+			data_info->reset_cnt = obj->integer.value;
+			break;
+		}
+	}
+	ret = 0;
+
+free_acpi_buffer:
+	ACPI_FREE(out_obj);
+
+	return ret;
+}
+
+static int set_pfru_log_level(int level)
+{
+	union acpi_object *out_obj, *obj, in_obj, in_buf;
+	struct pfru_telem_device *pf_telem_dev;
+	enum dsm_status status;
+	acpi_handle handle;
+	int ret = -EINVAL;
+
+	pf_telem_dev = get_pfru_telem_dev();
+	handle = ACPI_HANDLE(pf_telem_dev->dev);
+
+	memset(&in_obj, 0, sizeof(in_obj));
+	memset(&in_buf, 0, sizeof(in_buf));
+	in_obj.type = ACPI_TYPE_PACKAGE;
+	in_obj.package.count = 1;
+	in_obj.package.elements = &in_buf;
+	in_buf.type = ACPI_TYPE_INTEGER;
+	in_buf.integer.value = level;
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pf_telem_dev->uuid,
+					  pf_telem_dev->info.log_revid, FUNC_SET_LEV,
+					  &in_obj, ACPI_TYPE_PACKAGE);
+	if (!out_obj)
+		return -EINVAL;
+
+	obj = &out_obj->package.elements[0];
+	status = obj->integer.value;
+	if (status) {
+		pr_err("get MM telemetry level error status %d\n",
+		       status);
+		goto free_acpi_buffer;
+	}
+
+	obj = &out_obj->package.elements[1];
+	status = obj->integer.value;
+	if (status) {
+		pr_err("get MM telemetry level error extend status %d\n",
+		       status);
+		goto free_acpi_buffer;
+	}
+	ret = 0;
+
+free_acpi_buffer:
+	ACPI_FREE(out_obj);
+
+	return ret;
+}
+
+static int get_pfru_log_level(int *level)
+{
+	struct pfru_telem_device *pf_telem_dev;
+	union acpi_object *out_obj, *obj;
+	enum dsm_status status;
+	acpi_handle handle;
+	int ret = -EINVAL;
+
+	pf_telem_dev = get_pfru_telem_dev();
+	handle = ACPI_HANDLE(pf_telem_dev->dev);
+	out_obj = acpi_evaluate_dsm_typed(handle, &pf_telem_dev->uuid,
+					  pf_telem_dev->info.log_revid, FUNC_GET_LEV,
+					  NULL, ACPI_TYPE_PACKAGE);
+	if (!out_obj)
+		return -EINVAL;
+
+	obj = &out_obj->package.elements[0];
+	if (obj->type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+	status = obj->integer.value;
+	if (status) {
+		pr_err("get MM telemetry level error status %d\n",
+		       status);
+		goto free_acpi_buffer;
+	}
+
+	obj = &out_obj->package.elements[1];
+	if (obj->type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+	status = obj->integer.value;
+	if (status) {
+		pr_err("get MM telemetry level error status %d\n",
+		       status);
+		goto free_acpi_buffer;
+	}
+
+	obj = &out_obj->package.elements[2];
+	if (obj->type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+	*level = obj->integer.value;
+
+	ret = 0;
+
+free_acpi_buffer:
+	ACPI_FREE(out_obj);
+
+	return ret;
+}
+
+static int valid_log_level(int level)
+{
+	return (level == LOG_ERR) || (level == LOG_WARN) ||
+		(level == LOG_INFO) || (level == LOG_VERB);
+}
+
+static int valid_log_type(int type)
+{
+	return (type == LOG_EXEC_IDX) || (type == LOG_HISTORY_IDX);
+}
+
+static long pfru_telemetry_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+
+{
+	struct pfru_telem_device *pf_telem_dev;
+	struct telem_data_info data_info;
+	struct telem_info info;
+	void __user *p;
+	int ret = 0;
+
+	pf_telem_dev = get_pfru_telem_dev();
+	p = (void __user *)arg;
+
+	switch (cmd) {
+	case PFRU_LOG_IOC_SET_INFO:
+		if (copy_from_user(&info, p, sizeof(info)))
+			return -EFAULT;
+		if (valid_revid(info.log_revid))
+			pf_telem_dev->info.log_revid = info.log_revid;
+
+		if (valid_log_level(info.log_level)) {
+			ret = set_pfru_log_level(info.log_level);
+			if (ret)
+				return ret;
+			pf_telem_dev->info.log_level = info.log_level;
+		}
+		if (valid_log_type(info.log_type))
+			pf_telem_dev->info.log_type = info.log_type;
+		break;
+	case PFRU_LOG_IOC_GET_INFO:
+		ret = get_pfru_log_level(&info.log_level);
+		if (ret)
+			return ret;
+		info.log_type = pf_telem_dev->info.log_type;
+		info.log_revid = pf_telem_dev->info.log_revid;
+		if (copy_to_user(p, &info, sizeof(info)))
+			ret = -EFAULT;
+		break;
+	case PFRU_LOG_IOC_GET_DATA_INFO:
+		ret = get_pfru_data_info(&data_info, pf_telem_dev->info.log_type);
+		if (ret)
+			return ret;
+		if (copy_to_user(p, &data_info, sizeof(struct telem_data_info)))
+			ret = -EFAULT;
+		break;
+	default:
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+	return ret;
+}
+
+static ssize_t pfru_telemetry_read(struct file *filp, char __user *ubuf,
+				   size_t size, loff_t *off)
+{
+	struct pfru_telem_device *pf_telem_dev;
+	struct telem_data_info info;
+	phys_addr_t base_addr;
+	int buf_size, ret;
+	char *buf_ptr;
+
+	if (*off < 0)
+		return -EINVAL;
+
+	pf_telem_dev = get_pfru_telem_dev();
+
+	ret = get_pfru_data_info(&info, pf_telem_dev->info.log_type);
+	if (ret) {
+		pr_err("Could not get telemetry data info %d\n", ret);
+		return ret;
+	}
+
+	base_addr = (phys_addr_t)(info.chunk2_addr_lo |
+			(info.chunk2_addr_hi << 32));
+	if (!base_addr) {
+		pr_err("Telemetry data not ready\n");
+		return -EBUSY;
+	}
+
+	buf_size = info.max_data_size;
+	if (*off >= buf_size)
+		return 0;
+
+	buf_ptr = memremap(base_addr, buf_size, MEMREMAP_WB);
+	if (IS_ERR(buf_ptr))
+		return PTR_ERR(buf_ptr);
+
+	size = min_t(size_t, size, buf_size - *off);
+
+	ret = -EFAULT;
+	if (copy_to_user(ubuf, buf_ptr + *off, size))
+		goto out;
+	ret = 0;
+out:
+	memunmap(buf_ptr);
+
+	return ret ? ret : size;
+}
+
+#ifdef CONFIG_COMPAT
+static long compat_pfru_telemetry_ioctl(struct file *filep, unsigned int cmd,
+					unsigned long arg)
+{
+	return pfru_telemetry_ioctl(filep, cmd, arg);
+}
+#endif
+
+static const struct file_operations acpi_pfru_telemetry_fops = {
+	.owner		= THIS_MODULE,
+	.read		= pfru_telemetry_read,
+	.unlocked_ioctl = pfru_telemetry_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= compat_pfru_telemetry_ioctl,
+#endif
+	.llseek		= noop_llseek,
+};
+
+static struct miscdevice pfru_telemetry_misc_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "pfru_telemetry",
+	.nodename = "pfru/telemetry",
+	.fops = &acpi_pfru_telemetry_fops,
+};
+
+static int acpi_pfru_telemetry_remove(struct platform_device *pdev)
+{
+	misc_deregister(&pfru_telemetry_misc_dev);
+
+	return 0;
+}
+
+static int acpi_pfru_telemetry_probe(struct platform_device *pdev)
+{
+	struct pfru_telem_device *pf_telem_dev;
+	acpi_handle handle;
+	int ret;
+
+	pf_telem_dev = get_pfru_telem_dev();
+
+	ret = guid_parse(PFRU_TELEMETRY_UUID, &pf_telem_dev->uuid);
+	if (ret)
+		return ret;
+
+	pf_telem_dev->info.log_revid = 1;
+	pf_telem_dev->dev = &pdev->dev;
+	handle = ACPI_HANDLE(pf_telem_dev->dev);
+	if (!acpi_has_method(handle, "_DSM")) {
+		pr_err("Missing _DSM\n");
+		return -ENODEV;
+	}
+
+	ret = misc_register(&pfru_telemetry_misc_dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct acpi_device_id acpi_pfru_telemetry_ids[] = {
+	{"INTC1081", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, acpi_pfru_telemetry_ids);
+
+static struct platform_driver acpi_pfru_telemetry_driver = {
+	.driver = {
+		.name = "pfru_telemetry",
+		.acpi_match_table = acpi_pfru_telemetry_ids,
+	},
+	.probe = acpi_pfru_telemetry_probe,
+	.remove = acpi_pfru_telemetry_remove,
+};
+module_platform_driver(acpi_pfru_telemetry_driver);
+
+MODULE_DESCRIPTION("Platform Firmware Runtime Update Telemetry Service device driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/uapi/linux/pfru.h b/include/uapi/linux/pfru.h
index 81eb8ad5a57e..b4d5c0078cfb 100644
--- a/include/uapi/linux/pfru.h
+++ b/include/uapi/linux/pfru.h
@@ -103,4 +103,50 @@ struct updated_result {
 	unsigned long high_exec_time;
 };
 
+#define PFRU_TELEMETRY_UUID	"75191659-8178-4D9D-B88F-AC5E5E93E8BF"
+
+/* Telemetry structures. */
+struct telem_data_info {
+	enum dsm_status status;
+	enum dsm_status ext_status;
+	/* Maximum supported size of data of
+	 * all Data Chunks combined.
+	 */
+	unsigned long chunk1_addr_lo;
+	unsigned long chunk1_addr_hi;
+	unsigned long chunk2_addr_lo;
+	unsigned long chunk2_addr_hi;
+	int max_data_size;
+	int chunk1_size;
+	int chunk2_size;
+	int rollover_cnt;
+	int reset_cnt;
+};
+
+struct telem_info {
+	int log_level;
+	int log_type;
+	int log_revid;
+};
+
+/* Two logs: history and execution log */
+#define LOG_EXEC_IDX	0
+#define LOG_HISTORY_IDX	1
+#define NR_LOG_TYPE	2
+
+#define LOG_ERR		0
+#define LOG_WARN	1
+#define LOG_INFO	2
+#define LOG_VERB	4
+
+#define FUNC_SET_LEV		1
+#define FUNC_GET_LEV		2
+#define FUNC_GET_DATA		3
+
+#define LOG_NAME_SIZE		10
+
+#define PFRU_LOG_IOC_SET_INFO _IOW(PFRU_MAGIC, 0x05, struct telem_info)
+#define PFRU_LOG_IOC_GET_INFO _IOR(PFRU_MAGIC, 0x06, struct telem_info)
+#define PFRU_LOG_IOC_GET_DATA_INFO _IOR(PFRU_MAGIC, 0x07, struct telem_data_info)
+
 #endif /* __PFRU_H__ */
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry
  2021-09-07 15:08 [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers Chen Yu
                   ` (3 preceding siblings ...)
  2021-09-07 15:39 ` [PATCH 4/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry Chen Yu
@ 2021-09-07 15:40 ` Chen Yu
  2021-09-07 21:28   ` Shuah Khan
  2021-09-08  9:08   ` Mike Rapoport
  4 siblings, 2 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:40 UTC (permalink / raw)
  To: linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Chen Yu, Shuah Khan,
	linux-kselftest, Dou Shengnan

Introduce a simple test for Platform Firmware Runtime Update and Telemetry
drivers. It is based on ioctl to either update firmware driver or code injection,
and read corresponding PFRU Telemetry log into user space.

For example:

./pfru_test -h
usage: pfru_test [OPTIONS]
 code injection:
  -l, --load
  -s, --stage
  -a, --activate
  -u, --update [stage and activate]
  -q, --query
  -d, --revid update
 telemetry:
  -G, --getloginfo
  -T, --type(0:execution, 1:history)
  -L, --level(0, 1, 2, 4)
  -R, --read
  -D, --revid log

./pfru_test -G
 log_level:4
 log_type:0
 log_revid:2
 max_data_size:65536
 chunk1_size:0
 chunk2_size:1401
 rollover_cnt:0
 reset_cnt:4

./pfru_test -q
 code injection image type:794bf8b2-6e7b-454e-885f-3fb9bb185402
 fw_version:0
 code_rt_version:1
 driver update image type:0e5f0b14-f849-7945-ad81-bc7b6d2bb245
 drv_rt_version:0
 drv_svn:0
 platform id:39214663-b1a8-4eaa-9024-f2bb53ea4723
 oem id:a36db54f-ea2a-e14e-b7c4-b5780e51ba3d

Tested-by: Dou Shengnan <shengnanx.dou@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
---
 tools/testing/selftests/Makefile         |   1 +
 tools/testing/selftests/pfru/Makefile    |   7 +
 tools/testing/selftests/pfru/config      |   2 +
 tools/testing/selftests/pfru/pfru.h      | 152 +++++++++++
 tools/testing/selftests/pfru/pfru_test.c | 324 +++++++++++++++++++++++
 5 files changed, 486 insertions(+)
 create mode 100644 tools/testing/selftests/pfru/Makefile
 create mode 100644 tools/testing/selftests/pfru/config
 create mode 100644 tools/testing/selftests/pfru/pfru.h
 create mode 100644 tools/testing/selftests/pfru/pfru_test.c

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index fb010a35d61a..c8b53a2c4450 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -56,6 +56,7 @@ TARGETS += seccomp
 TARGETS += sgx
 TARGETS += sigaltstack
 TARGETS += size
+TARGETS += pfru
 TARGETS += sparc64
 TARGETS += splice
 TARGETS += static_keys
diff --git a/tools/testing/selftests/pfru/Makefile b/tools/testing/selftests/pfru/Makefile
new file mode 100644
index 000000000000..c61916ccf637
--- /dev/null
+++ b/tools/testing/selftests/pfru/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+CFLAGS += -Wall -O2
+LDLIBS := -luuid
+
+TEST_GEN_PROGS := pfru_test
+include ../lib.mk
diff --git a/tools/testing/selftests/pfru/config b/tools/testing/selftests/pfru/config
new file mode 100644
index 000000000000..37f53609acbd
--- /dev/null
+++ b/tools/testing/selftests/pfru/config
@@ -0,0 +1,2 @@
+CONFIG_ACPI_PFRU=m
+CONFIG_ACPI_PFRU_TELEMETRY=m
diff --git a/tools/testing/selftests/pfru/pfru.h b/tools/testing/selftests/pfru/pfru.h
new file mode 100644
index 000000000000..8cd4ed80b161
--- /dev/null
+++ b/tools/testing/selftests/pfru/pfru.h
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Platform Firmware Runtime Update header
+ *
+ * Copyright(c) 2021 Intel Corporation. All rights reserved.
+ */
+#ifndef __PFRU_H__
+#define __PFRU_H__
+
+#include <linux/ioctl.h>
+#include <uuid/uuid.h>
+
+#define PFRU_UUID		"ECF9533B-4A3C-4E89-939E-C77112601C6D"
+#define PFRU_CODE_INJ_UUID		"B2F84B79-7B6E-4E45-885F-3FB9BB185402"
+#define PFRU_DRV_UPDATE_UUID		"4569DD8C-75F1-429A-A3D6-24DE8097A0DF"
+
+#define FUNC_STANDARD_QUERY	0
+#define FUNC_QUERY_UPDATE_CAP	1
+#define FUNC_QUERY_BUF		2
+#define FUNC_START		3
+
+#define CODE_INJECT_TYPE	1
+#define DRIVER_UPDATE_TYPE	2
+
+#define REVID_1		1
+#define REVID_2		2
+
+#define PFRU_MAGIC 0xEE
+
+#define PFRU_IOC_SET_REV _IOW(PFRU_MAGIC, 0x01, unsigned int)
+#define PFRU_IOC_STAGE _IOW(PFRU_MAGIC, 0x02, unsigned int)
+#define PFRU_IOC_ACTIVATE _IOW(PFRU_MAGIC, 0x03, unsigned int)
+#define PFRU_IOC_STAGE_ACTIVATE _IOW(PFRU_MAGIC, 0x04, unsigned int)
+
+static inline int valid_revid(int id)
+{
+	return (id == REVID_1) || (id == REVID_2);
+}
+
+/* Capsule file payload header */
+struct payload_hdr {
+	__u32	sig;
+	__u32	hdr_version;
+	__u32	hdr_size;
+	__u32	hw_ver;
+	__u32	rt_ver;
+	uuid_t	platform_id;
+};
+
+enum start_action {
+	START_STAGE,
+	START_ACTIVATE,
+	START_STAGE_ACTIVATE,
+};
+
+enum dsm_status {
+	DSM_SUCCEED,
+	DSM_FUNC_NOT_SUPPORT,
+	DSM_INVAL_INPUT,
+	DSM_HARDWARE_ERR,
+	DSM_RETRY_SUGGESTED,
+	DSM_UNKNOWN,
+	DSM_FUNC_SPEC_ERR,
+};
+
+struct update_cap_info {
+	enum dsm_status status;
+	int update_cap;
+
+	uuid_t code_type;
+	int fw_version;
+	int code_rt_version;
+
+	uuid_t drv_type;
+	int drv_rt_version;
+	int drv_svn;
+
+	uuid_t platform_id;
+	uuid_t oem_id;
+
+	char oem_info[];
+};
+
+struct com_buf_info {
+	enum dsm_status status;
+	enum dsm_status ext_status;
+	unsigned long addr_lo;
+	unsigned long addr_hi;
+	int buf_size;
+};
+
+struct capsulate_buf_info {
+	unsigned long src;
+	int size;
+};
+
+struct updated_result {
+	enum dsm_status status;
+	enum dsm_status ext_status;
+	unsigned long low_auth_time;
+	unsigned long high_auth_time;
+	unsigned long low_exec_time;
+	unsigned long high_exec_time;
+};
+
+#define PFRU_TELEMETRY_UUID	"75191659-8178-4D9D-B88F-AC5E5E93E8BF"
+
+/* Telemetry structures. */
+struct telem_data_info {
+	enum dsm_status status;
+	enum dsm_status ext_status;
+	/* Maximum supported size of data of
+	 * all Data Chunks combined.
+	 */
+	unsigned long chunk1_addr_lo;
+	unsigned long chunk1_addr_hi;
+	unsigned long chunk2_addr_lo;
+	unsigned long chunk2_addr_hi;
+	int max_data_size;
+	int chunk1_size;
+	int chunk2_size;
+	int rollover_cnt;
+	int reset_cnt;
+};
+
+struct telem_info {
+	int log_level;
+	int log_type;
+	int log_revid;
+};
+
+/* Two logs: history and execution log */
+#define LOG_EXEC_IDX	0
+#define LOG_HISTORY_IDX	1
+#define NR_LOG_TYPE	2
+
+#define LOG_ERR		0
+#define LOG_WARN	1
+#define LOG_INFO	2
+#define LOG_VERB	4
+
+#define FUNC_SET_LEV		1
+#define FUNC_GET_LEV		2
+#define FUNC_GET_DATA		3
+
+#define LOG_NAME_SIZE		10
+
+#define PFRU_LOG_IOC_SET_INFO _IOW(PFRU_MAGIC, 0x05, struct telem_info)
+#define PFRU_LOG_IOC_GET_INFO _IOR(PFRU_MAGIC, 0x06, struct telem_info)
+#define PFRU_LOG_IOC_GET_DATA_INFO _IOR(PFRU_MAGIC, 0x07, struct telem_data_info)
+
+#endif /* __PFRU_H__ */
diff --git a/tools/testing/selftests/pfru/pfru_test.c b/tools/testing/selftests/pfru/pfru_test.c
new file mode 100644
index 000000000000..d24d79d3836e
--- /dev/null
+++ b/tools/testing/selftests/pfru/pfru_test.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tests Runtime Update/Telemetry (see Documentation/x86/pfru_update.rst)
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include "pfru.h"
+
+#define MAX_LOG_SIZE 65536
+
+struct update_cap_info cap_info;
+struct com_buf_info buf_info;
+struct capsulate_buf_info image_info;
+struct telem_data_info data_info;
+char *capsule_name;
+int action, query_cap, log_type, log_level, log_read, log_getinfo,
+	revid, log_revid;
+int set_log_level, set_log_type,
+	set_revid, set_log_revid;
+
+char *progname;
+
+static int valid_log_level(int level)
+{
+	return (level == LOG_ERR) || (level == LOG_WARN) ||
+		(level == LOG_INFO) || (level == LOG_VERB);
+}
+
+static int valid_log_type(int type)
+{
+	return (type == LOG_EXEC_IDX) || (type == LOG_HISTORY_IDX);
+}
+
+static void help(void)
+{
+	fprintf(stderr,
+		"usage: %s [OPTIONS]\n"
+		" code injection:\n"
+		"  -l, --load\n"
+		"  -s, --stage\n"
+		"  -a, --activate\n"
+		"  -u, --update [stage and activate]\n"
+		"  -q, --query\n"
+		"  -d, --revid update\n"
+		" telemetry:\n"
+		"  -G, --getloginfo\n"
+		"  -T, --type(0:execution, 1:history)\n"
+		"  -L, --level(0, 1, 2, 4)\n"
+		"  -R, --read\n"
+		"  -D, --revid log\n",
+		progname);
+}
+
+char *option_string = "l:sauqd:GT:L:RD:h";
+static struct option long_options[] = {
+	{"load", required_argument, 0, 'l'},
+	{"stage", no_argument, 0, 's'},
+	{"activate", no_argument, 0, 'a'},
+	{"update", no_argument, 0, 'u'},
+	{"query", no_argument, 0, 'q'},
+	{"getloginfo", no_argument, 0, 'G'},
+	{"type", required_argument, 0, 'T'},
+	{"level", required_argument, 0, 'L'},
+	{"read", no_argument, 0, 'R'},
+	{"setrev", required_argument, 0, 'd'},
+	{"setrevlog", required_argument, 0, 'D'},
+	{"help", no_argument, 0, 'h'},
+	{}
+};
+
+static void parse_options(int argc, char **argv)
+{
+	char *pathname;
+	int c;
+
+	pathname = strdup(argv[0]);
+	progname = basename(pathname);
+
+	while (1) {
+		int option_index = 0;
+
+		c = getopt_long(argc, argv, option_string,
+				long_options, &option_index);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'l':
+			capsule_name = optarg;
+			break;
+		case 's':
+			action = 1;
+			break;
+		case 'a':
+			action = 2;
+			break;
+		case 'u':
+			action = 3;
+			break;
+		case 'q':
+			query_cap = 1;
+			break;
+		case 'G':
+			log_getinfo = 1;
+			break;
+		case 'T':
+			log_type = atoi(optarg);
+			set_log_type = 1;
+			break;
+		case 'L':
+			log_level = atoi(optarg);
+			set_log_level = 1;
+			break;
+		case 'R':
+			log_read = 1;
+			break;
+		case 'd':
+			revid = atoi(optarg);
+			set_revid = 1;
+			break;
+		case 'D':
+			log_revid = atoi(optarg);
+			set_log_revid = 1;
+			break;
+		case 'h':
+			help();
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+void print_cap(struct update_cap_info *cap)
+{
+	char *uuid = malloc(37);
+
+	if (!uuid) {
+		perror("Can not allocate uuid buffer\n");
+		exit(1);
+	}
+	uuid_unparse(cap->code_type, uuid);
+	printf("code injection image type:%s\n", uuid);
+	printf("fw_version:%d\n", cap->fw_version);
+	printf("code_rt_version:%d\n", cap->code_rt_version);
+
+	uuid_unparse(cap->drv_type, uuid);
+	printf("driver update image type:%s\n", uuid);
+	printf("drv_rt_version:%d\n", cap->drv_rt_version);
+	printf("drv_svn:%d\n", cap->drv_svn);
+
+	uuid_unparse(cap->platform_id, uuid);
+	printf("platform id:%s\n", uuid);
+	uuid_unparse(cap->oem_id, uuid);
+	printf("oem id:%s\n", uuid);
+
+	free(uuid);
+}
+
+int main(int argc, char *argv[])
+{
+	int fd_update, fd_log, fd_capsule;
+	struct telem_data_info data_info;
+	struct telem_info info;
+	struct update_cap_info cap;
+	void *addr_map_capsule;
+	struct stat st;
+	char *log_buf;
+	int ret = 0;
+
+	parse_options(argc, argv);
+
+	fd_log = open("/dev/pfru/telemetry", O_RDWR);
+	if (fd_log < 0) {
+		perror("Cannot open telemetry device...");
+		return 1;
+	}
+	fd_update = open("/dev/pfru/update", O_RDWR);
+	if (fd_update < 0) {
+		perror("Cannot open code injection device...");
+		return 1;
+	}
+
+	if (query_cap) {
+		ret = read(fd_update, &cap, sizeof(cap));
+		if (ret == -1) {
+			perror("Read error.");
+			return 1;
+		}
+		print_cap(&cap);
+	}
+
+	if (log_getinfo) {
+		ret = ioctl(fd_log, PFRU_LOG_IOC_GET_DATA_INFO, &data_info);
+		if (ret) {
+			perror("Get log data info failed.");
+			return 1;
+		}
+		ret = ioctl(fd_log, PFRU_LOG_IOC_GET_INFO, &info);
+		if (ret) {
+			perror("Get log info failed.");
+			return 1;
+		}
+		printf("log_level:%d\n", info.log_level);
+		printf("log_type:%d\n", info.log_type);
+		printf("log_revid:%d\n", info.log_revid);
+		printf("max_data_size:%d\n", data_info.max_data_size);
+		printf("chunk1_size:%d\n", data_info.chunk1_size);
+		printf("chunk2_size:%d\n", data_info.chunk2_size);
+		printf("rollover_cnt:%d\n", data_info.rollover_cnt);
+		printf("reset_cnt:%d\n", data_info.reset_cnt);
+
+		return 0;
+	}
+
+	info.log_level = -1;
+	info.log_type = -1;
+	info.log_revid = -1;
+
+	if (set_log_level) {
+		if (!valid_log_level(log_level)) {
+			printf("Invalid log level %d\n",
+			       log_level);
+		} else {
+			info.log_level = log_level;
+		}
+	}
+	if (set_log_type) {
+		if (!valid_log_type(log_type)) {
+			printf("Invalid log type %d\n",
+			       log_type);
+		} else {
+			info.log_type = log_type;
+		}
+	}
+	if (set_log_revid) {
+		if (!valid_revid(log_revid)) {
+			printf("Invalid log revid %d\n",
+			       log_revid);
+		} else {
+			info.log_revid = log_revid;
+		}
+	}
+
+	ret = ioctl(fd_log, PFRU_LOG_IOC_SET_INFO, &info);
+	if (ret) {
+		perror("Log information set failed.(log_level, log_type, log_revid)");
+		return 1;
+	}
+
+	if (set_revid) {
+		ret = ioctl(fd_update, PFRU_IOC_SET_REV, &revid);
+		if (ret) {
+			perror("mru update revid set failed");
+			return 1;
+		}
+		printf("mru update revid set to %d\n", revid);
+	}
+
+	if (capsule_name) {
+		fd_capsule = open(capsule_name, O_RDONLY);
+		if (fd_capsule < 0) {
+			perror("Can not open capsule file...");
+			return 1;
+		}
+		if (fstat(fd_capsule, &st) < 0) {
+			perror("Can not fstat capsule file...");
+			return 1;
+		}
+		addr_map_capsule = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
+					fd_capsule, 0);
+		if (addr_map_capsule == MAP_FAILED) {
+			perror("Failed to mmap capsule file.");
+			return 1;
+		}
+		ret = write(fd_update, (char *)addr_map_capsule, st.st_size);
+		printf("Load %d bytes of capsule file into the system\n",
+		       ret);
+		if (ret == -1) {
+			perror("Failed to load capsule file");
+			return 1;
+		}
+		munmap(addr_map_capsule, st.st_size);
+		printf("Load done.\n");
+	}
+
+	if (action) {
+		if (action == 1)
+			ret = ioctl(fd_update, PFRU_IOC_STAGE, NULL);
+		else if (action == 2)
+			ret = ioctl(fd_update, PFRU_IOC_ACTIVATE, NULL);
+		else if (action == 3)
+			ret = ioctl(fd_update, PFRU_IOC_STAGE_ACTIVATE, NULL);
+		else
+			return 1;
+		printf("Update finished, return %d\n", ret);
+	}
+
+	if (log_read) {
+		log_buf = malloc(MAX_LOG_SIZE + 1);
+		if (!log_buf) {
+			perror("log_buf allocate failed.");
+			return 1;
+		}
+		ret = read(fd_log, log_buf, MAX_LOG_SIZE);
+		if (ret == -1) {
+			perror("Read error.");
+			return 1;
+		}
+		log_buf[ret] = '\0';
+		printf("%s\n", log_buf);
+		free(log_buf);
+	}
+
+	return 0;
+}
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation
  2021-09-07 15:23   ` Jonathan Corbet
@ 2021-09-07 15:48     ` Chen Yu
  0 siblings, 0 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-07 15:48 UTC (permalink / raw)
  To: Jonathan Corbet
  Cc: linux-acpi, linux-kernel, Rafael J. Wysocki, Len Brown,
	Dan Williams, Andy Shevchenko, Aubrey Li, Ashok Raj,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin,
	linux-doc, x86

Hi Jon,
On Tue, Sep 07, 2021 at 09:23:53AM -0600, Jonathan Corbet wrote:
> Thanks for adding to the documentation.  I have a few nits for you...
> 
Thank you very much for your comments.
> Chen Yu <yu.c.chen@intel.com> writes:
> 
> > Add the Platform Firmware Runtime Update/Telemetry documentation.
> >
> > Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> > ---
> >  Documentation/x86/pfru.rst | 98 ++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 98 insertions(+)
> >  create mode 100644 Documentation/x86/pfru.rst
> 
> When you add a new RST file, you also need to find a spot for it in
> index.rst so it becomes part of the docs build.
> 
I see. Will do in next version.
> > diff --git a/Documentation/x86/pfru.rst b/Documentation/x86/pfru.rst
> > new file mode 100644
> > index 000000000000..321729f46737
> > --- /dev/null
> > +++ b/Documentation/x86/pfru.rst
> > @@ -0,0 +1,98 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +========================================================
> > +The Linux Platform Firmware Runtime Update and Telemetry
> > +========================================================
> > +
> > +According to the specification of <Management Mode Firmware Runtime Update>[1],
> > +certain computing systems require high Service Level Agreements (SLAs) where
> > +system reboot fewer firmware updates are required to deploy firmware changes
> > +to address bug fixes, security updates and to debug and root cause issues. This
> > +technology is called Intel Seamless Update. The management mode (MM),
> > +UEFI runtime services and ACPI services handle most of the system runtime
> > +functions. Changing the MM code execution during runtime is called MM Runtime
> > +Update. Since the "MM" acronyms might be misunderstood as "Memory Management",
> > +this driver uses "Platform Firmware Runtime Update"(PFRU)
> > +
> > +PFRU provides the following facilities: Performs a runtime firmware driver update
> > +and activate. Ability to inject firmware code at runtime, for dynamic instrumentation.
> > +PFRU Telemetry is a service which allows Runtime Update handler to produce telemetry
> > +data to upper layer OS consumer at runtime. The OS provides interfaces to let the
> > +users query the telemetry data via read operations. The specification specifies the
> > +interface and recommended policy to extract the data, the format and use are left to
> > +individual OEM's and BIOS implementations on what that data represents.
> 
> Sticking to the 80-column limit is preferable; it keeps the text
> readable. 
> 
Okay, will do.
> > +PFRU interfaces
> > +=====================
> 
> Underline lengths should match the title text, or Sphinx will get grumpy
> with you.
> 
Got it, will fix it.
> > +The user space tool manipulates on /dev/pfru/update for code injection and
> > +driver update. PFRU stands for Platform Firmware Runtime Update, and the /dev/pfru
> > +directory might be reserved for future usage.
> > +
> > + 1. mmap the capsule file
> > +    fd_capsule = open("capsule.cap", O_RDONLY);
> > +    fstat(fd_capsule, &stat);
> > +    addr = mmap(0, stat.st_size, PROT_READ, fd_capsule);
> 
> These will not render the way you would like; you'll want to use literal
> blocks for the code samples.
> 
Okay, I'll fix it.

thanks,
Chenyu

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures
  2021-09-07 15:17 ` [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures Chen Yu
@ 2021-09-07 16:06   ` Ard Biesheuvel
  2021-09-07 23:56     ` Chen Yu
  0 siblings, 1 reply; 16+ messages in thread
From: Ard Biesheuvel @ 2021-09-07 16:06 UTC (permalink / raw)
  To: Chen Yu
  Cc: ACPI Devel Maling List, Linux Kernel Mailing List,
	Rafael J. Wysocki, Len Brown, Dan Williams, Andy Shevchenko,
	Aubrey Li, Ashok Raj, linux-efi

On Tue, 7 Sept 2021 at 17:12, Chen Yu <yu.c.chen@intel.com> wrote:
>
> Platform Firmware Runtime Update image starts with UEFI headers, and the headers
> are defined in UEFI specification, but some of them have not been defined in the
> kernel yet.
>
> For example, the header layout of a capsule file looks like this:
>
> EFI_CAPSULE_HEADER
> EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER
> EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER
> EFI_FIRMWARE_IMAGE_AUTHENTICATION
>
> These structures would be used by the Platform Firmware Runtime Update
> driver to parse the format of capsule file to verify if the corresponding
> version number is valid. The EFI_CAPSULE_HEADER has been defined in the
> kernel, however the rest are not, thus introduce corresponding UEFI structures
> accordingly.
>
> The reason why efi_manage_capsule_header_t and efi_manage_capsule_image_header_t
> are packedi might be that:
> According to the uefi spec,
> [Figure 23-6 Firmware Management and Firmware Image Management headers]
> EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER is located at the lowest offset within
> the body of the capsule. And this structure is designed to be unaligned to save
> space, because in this way the adjacent drivers and binary payload elements could
> start on byte boundary with no padding. And the
> EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER is at the head of each payload, so
> packing this structure also makes room for more data.
>
> Signed-off-by: Chen Yu <yu.c.chen@intel.com>

Who is going to use these definitions? I only see this patch, where is
the rest of the series?

> ---
>  include/linux/efi.h | 50 +++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 50 insertions(+)
>
> diff --git a/include/linux/efi.h b/include/linux/efi.h
> index 6b5d36babfcc..19ff834e1388 100644
> --- a/include/linux/efi.h
> +++ b/include/linux/efi.h
> @@ -148,6 +148,56 @@ typedef struct {
>         u32 imagesize;
>  } efi_capsule_header_t;
>
> +#pragma pack(1)
> +
> +/* EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER */
> +typedef struct {
> +       u32     ver;
> +       u16     emb_drv_cnt;
> +       u16     payload_cnt;
> +       /*
> +        * Variable array indicated by number of
> +        * (emb_drv_cnt + payload_cnt)
> +        */
> +       u64     offset_list[];
> +} efi_manage_capsule_header_t;
> +
> +/* EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER */
> +typedef struct {
> +       u32     ver;
> +       guid_t  image_type_id;
> +       u8      image_index;
> +       u8      reserved_bytes[3];
> +       u32     image_size;
> +       u32     vendor_code_size;
> +       /* ver = 2. */
> +       u64     hw_ins;
> +       /* ver = v3. */
> +       u64     capsule_support;
> +} efi_manage_capsule_image_header_t;
> +
> +#pragma pack()
> +
> +/* WIN_CERTIFICATE */
> +typedef struct {
> +       u32     len;
> +       u16     rev;
> +       u16     cert_type;
> +} win_cert_t;
> +
> +/* WIN_CERTIFICATE_UEFI_GUID */
> +typedef struct {
> +       win_cert_t      hdr;
> +       guid_t          cert_type;
> +       u8              cert_data[];
> +} win_cert_uefi_guid_t;
> +
> +/* EFI_FIRMWARE_IMAGE_AUTHENTICATIO */
> +typedef struct {
> +       u64                             mon_count;
> +       win_cert_uefi_guid_t            auth_info;
> +} efi_image_auth_t;
> +
>  /*
>   * EFI capsule flags
>   */
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry
  2021-09-07 15:40 ` [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry Chen Yu
@ 2021-09-07 21:28   ` Shuah Khan
  2021-09-14  6:46     ` Chen Yu
  2021-09-08  9:08   ` Mike Rapoport
  1 sibling, 1 reply; 16+ messages in thread
From: Shuah Khan @ 2021-09-07 21:28 UTC (permalink / raw)
  To: Chen Yu, linux-acpi
  Cc: linux-kernel, Rafael J. Wysocki, Len Brown, Dan Williams,
	Andy Shevchenko, Aubrey Li, Ashok Raj, Shuah Khan,
	linux-kselftest, Dou Shengnan, Shuah Khan

On 9/7/21 9:40 AM, Chen Yu wrote:
> Introduce a simple test for Platform Firmware Runtime Update and Telemetry
> drivers. It is based on ioctl to either update firmware driver or code injection,
> and read corresponding PFRU Telemetry log into user space.
> 

A few things to consider and add handling for them in the
test.

What happens when non-root user runs this test?
What happens when the pfru device doesn't exist?


[snip]

> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int fd_update, fd_log, fd_capsule;
> +	struct telem_data_info data_info;
> +	struct telem_info info;
> +	struct update_cap_info cap;
> +	void *addr_map_capsule;
> +	struct stat st;
> +	char *log_buf;
> +	int ret = 0;
> +
> +	parse_options(argc, argv);
> +
> +	fd_log = open("/dev/pfru/telemetry", O_RDWR);
> +	if (fd_log < 0) {
> +		perror("Cannot open telemetry device...");
> +		return 1;
> +	}

Is this considered an error or unsupported?

> +	fd_update = open("/dev/pfru/update", O_RDWR);
> +	if (fd_update < 0) {
> +		perror("Cannot open code injection device...");
> +		return 1;
> +	}
> +

Same here. If test is run on platform with pfru test should skip
instead of reporting failure/error.

thanks,
-- Shuah

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures
  2021-09-07 16:06   ` Ard Biesheuvel
@ 2021-09-07 23:56     ` Chen Yu
  0 siblings, 0 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-07 23:56 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: ACPI Devel Maling List, Linux Kernel Mailing List,
	Rafael J. Wysocki, Len Brown, Dan Williams, Andy Shevchenko,
	Aubrey Li, Ashok Raj, linux-efi

On Tue, Sep 07, 2021 at 06:06:28PM +0200, Ard Biesheuvel wrote:
> On Tue, 7 Sept 2021 at 17:12, Chen Yu <yu.c.chen@intel.com> wrote:
> >
> > Platform Firmware Runtime Update image starts with UEFI headers, and the headers
> > are defined in UEFI specification, but some of them have not been defined in the
> > kernel yet.
> >
> > For example, the header layout of a capsule file looks like this:
> >
> > EFI_CAPSULE_HEADER
> > EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER
> > EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER
> > EFI_FIRMWARE_IMAGE_AUTHENTICATION
> >
> > These structures would be used by the Platform Firmware Runtime Update
> > driver to parse the format of capsule file to verify if the corresponding
> > version number is valid. The EFI_CAPSULE_HEADER has been defined in the
> > kernel, however the rest are not, thus introduce corresponding UEFI structures
> > accordingly.
> >
> > The reason why efi_manage_capsule_header_t and efi_manage_capsule_image_header_t
> > are packedi might be that:
> > According to the uefi spec,
> > [Figure 23-6 Firmware Management and Firmware Image Management headers]
> > EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER is located at the lowest offset within
> > the body of the capsule. And this structure is designed to be unaligned to save
> > space, because in this way the adjacent drivers and binary payload elements could
> > start on byte boundary with no padding. And the
> > EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER is at the head of each payload, so
> > packing this structure also makes room for more data.
> >
> > Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> 
> Who is going to use these definitions? I only see this patch, where is
> the rest of the series?
>
Sorry, Ard, and thanks for taking a look at this patch.

The user of this definitions is an ACPI device driver, named Platform Firmware
Runtime Update driver. This driver is designed to do firmware update via SMI.
This driver will do sanity check of the capsule file using these structures before
the file is passed to SMI. We put this check in kernel space rather than leaving
it to SMI because the latter is very costly.

The series patch is at:
https://patchwork.kernel.org/project/linux-acpi/list/?series=543211
and the user is mainly PATCH 3/5:
https://patchwork.kernel.org/project/linux-acpi/patch/9b2bd7d1e40633ce6f4845fb5c9e30a3faad5e7a.1631025237.git.yu.c.chen@intel.com/
in valid_version().

thanks,
Chenyu

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver
  2021-09-07 15:27 ` [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver Chen Yu
@ 2021-09-08  9:04   ` Mike Rapoport
  2021-09-14  5:54     ` Chen Yu
  0 siblings, 1 reply; 16+ messages in thread
From: Mike Rapoport @ 2021-09-08  9:04 UTC (permalink / raw)
  To: Chen Yu
  Cc: linux-acpi, linux-kernel, Rafael J. Wysocki, Len Brown,
	Dan Williams, Andy Shevchenko, Aubrey Li, Ashok Raj,
	Jonathan Corbet, Greg Kroah-Hartman, Hans de Goede,
	Maximilian Luz, Alexander Graf, Jarkko Sakkinen, Shuo Liu,
	Hannes Reinecke, Ioana Ciornei, Jiri Slaby, Andra Paraschiv,
	Randy Dunlap, Ben Widawsky, linux-doc

Hi,

On Tue, Sep 07, 2021 at 11:27:42PM +0800, Chen Yu wrote:
> Introduce the pfru_update driver which can be used for Platform Firmware
> Runtime code injection and driver update. The user is expected to provide
> the update firmware in the form of capsule file, and pass it to the driver
> via ioctl. Then the driver would hand this capsule file to the Platform
> Firmware Runtime Update via the ACPI device _DSM method. At last the low
> level Management Mode would do the firmware update.
> 
> Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> ---
>  .../userspace-api/ioctl/ioctl-number.rst      |   1 +
>  drivers/acpi/Kconfig                          |   1 +
>  drivers/acpi/Makefile                         |   1 +
>  drivers/acpi/pfru/Kconfig                     |  15 +
>  drivers/acpi/pfru/Makefile                    |   2 +
>  drivers/acpi/pfru/pfru_update.c               | 544 ++++++++++++++++++
>  include/uapi/linux/pfru.h                     | 106 ++++
>  7 files changed, 670 insertions(+)
>  create mode 100644 drivers/acpi/pfru/Kconfig
>  create mode 100644 drivers/acpi/pfru/Makefile
>  create mode 100644 drivers/acpi/pfru/pfru_update.c
>  create mode 100644 include/uapi/linux/pfru.h
> 
> diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> index 1409e40e6345..976920c404dc 100644
> --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> @@ -365,6 +365,7 @@ Code  Seq#    Include File                                           Comments
>                                                                       <mailto:aherrman@de.ibm.com>
>  0xE5  00-3F  linux/fuse.h
>  0xEC  00-01  drivers/platform/chrome/cros_ec_dev.h                   ChromeOS EC driver
> +0xEE  00-1F  uapi/linux/pfru.h                                       Platform Firmware Runtime Update and Telemetry
>  0xF3  00-3F  drivers/usb/misc/sisusbvga/sisusb.h                     sisfb (in development)
>                                                                       <mailto:thomas@winischhofer.net>
>  0xF6  all                                                            LTTng Linux Trace Toolkit Next Generation
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index 8f9940f40baa..e7f721bed20d 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -482,6 +482,7 @@ source "drivers/acpi/nfit/Kconfig"
>  source "drivers/acpi/numa/Kconfig"
>  source "drivers/acpi/apei/Kconfig"
>  source "drivers/acpi/dptf/Kconfig"
> +source "drivers/acpi/pfru/Kconfig"
>  
>  config ACPI_WATCHDOG
>  	bool
> diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
> index 3018714e87d9..9c2c5ddff6ec 100644
> --- a/drivers/acpi/Makefile
> +++ b/drivers/acpi/Makefile
> @@ -102,6 +102,7 @@ obj-$(CONFIG_ACPI_CPPC_LIB)	+= cppc_acpi.o
>  obj-$(CONFIG_ACPI_SPCR_TABLE)	+= spcr.o
>  obj-$(CONFIG_ACPI_DEBUGGER_USER) += acpi_dbg.o
>  obj-$(CONFIG_ACPI_PPTT) 	+= pptt.o
> +obj-$(CONFIG_ACPI_PFRU)		+= pfru/
>  
>  # processor has its own "processor." module_param namespace
>  processor-y			:= processor_driver.o
> diff --git a/drivers/acpi/pfru/Kconfig b/drivers/acpi/pfru/Kconfig
> new file mode 100644
> index 000000000000..3f31b7d95f3b
> --- /dev/null
> +++ b/drivers/acpi/pfru/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0
> +config ACPI_PFRU
> +	tristate "ACPI Platform Firmware Runtime Update (PFRU)"
> +	depends on 64BIT
> +	help
> +	  In order to reduce the system reboot times and update the platform firmware
> +	  in time, Platform Firmware Runtime Update is leveraged to patch the system
> +	  without reboot. This driver supports Platform Firmware Runtime Update,
> +	  which is composed of two parts: code injection and driver update.
> +
> +	  For more information, see:
> +	  <file:Documentation/x86/pfru_update.rst>
> +
> +	  To compile this driver as a module, choose M here:
> +	  the module will be called pfru_update.
> diff --git a/drivers/acpi/pfru/Makefile b/drivers/acpi/pfru/Makefile
> new file mode 100644
> index 000000000000..098cbe80cf3d
> --- /dev/null
> +++ b/drivers/acpi/pfru/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_ACPI_PFRU) += pfru_update.o
> diff --git a/drivers/acpi/pfru/pfru_update.c b/drivers/acpi/pfru/pfru_update.c
> new file mode 100644
> index 000000000000..3ddf42e6d355
> --- /dev/null
> +++ b/drivers/acpi/pfru/pfru_update.c
> @@ -0,0 +1,544 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ACPI Platform Firmware Runtime Update Device Driver
> + *
> + * Copyright (C) 2021 Intel Corporation
> + * Author: Chen Yu <yu.c.chen@intel.com>
> + */
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/efi.h>
> +#include <linux/err.h>
> +#include <linux/errno.h>
> +#include <linux/file.h>
> +#include <linux/fs.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/string.h>
> +#include <linux/uaccess.h>
> +#include <linux/uio.h>
> +#include <linux/uuid.h>
> +#include <uapi/linux/pfru.h>
> +
> +struct pfru_device {
> +	guid_t uuid;
> +	int rev_id;
> +	struct device *dev;
> +};
> +
> +static struct pfru_device pfru_dev;

Please don't presume a single instance and update the driver to work with
allocated pfru_device objects.

> +static struct pfru_device *get_pfru_dev(void)
> +{
> +	return &pfru_dev;
> +}
> +
> +static int query_capability(struct update_cap_info *cap)
> +{
> +	union acpi_object *out_obj;
> +	struct pfru_device *pf_dev;
> +	acpi_handle handle;
> +	int i, ret = -EINVAL;
> +
> +	pf_dev = get_pfru_dev();
> +	handle = ACPI_HANDLE(pf_dev->dev);
> +
> +	out_obj = acpi_evaluate_dsm_typed(handle, &pf_dev->uuid,
> +					  pf_dev->rev_id, FUNC_QUERY_UPDATE_CAP,
> +					  NULL, ACPI_TYPE_PACKAGE);
> +	if (!out_obj)
> +		return -EINVAL;
> +
> +	for (i = 0; i < out_obj->package.count; i++) {
> +		union acpi_object *obj = &out_obj->package.elements[i];
> +
> +		switch (i) {
> +		case 0:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			cap->status = obj->integer.value;
> +			break;
> +		case 1:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			cap->update_cap = obj->integer.value;
> +			break;
> +		case 2:
> +			if (obj->type != ACPI_TYPE_BUFFER)
> +				goto free_acpi_buffer;
> +			memcpy(&cap->code_type, obj->buffer.pointer,
> +			       obj->buffer.length);
> +			break;
> +		case 3:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			cap->fw_version = obj->integer.value;
> +			break;
> +		case 4:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			cap->code_rt_version = obj->integer.value;
> +			break;
> +		case 5:
> +			if (obj->type != ACPI_TYPE_BUFFER)
> +				goto free_acpi_buffer;
> +			memcpy(&cap->drv_type, obj->buffer.pointer,
> +			       obj->buffer.length);
> +			break;
> +		case 6:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			cap->drv_rt_version = obj->integer.value;
> +			break;
> +		case 7:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			cap->drv_svn = obj->integer.value;
> +			break;
> +		case 8:
> +			if (obj->type != ACPI_TYPE_BUFFER)
> +				goto free_acpi_buffer;
> +			memcpy(&cap->platform_id, obj->buffer.pointer,
> +			       obj->buffer.length);
> +			break;
> +		case 9:
> +			if (obj->type != ACPI_TYPE_BUFFER)
> +				goto free_acpi_buffer;
> +			memcpy(&cap->oem_id, obj->buffer.pointer,
> +			       obj->buffer.length);
> +			break;

Please get rid of the magic numbers and add a default clause with the error
handling. Nothing guaranties that there will be exactly 10 fields in the
out_obj.

Also, the obj->type checks could move outside the switch, with an if or a
static mapping between the field number and the desired type.


> +		}
> +	}
> +	ret = 0;
> +
> +free_acpi_buffer:
> +	ACPI_FREE(out_obj);
> +
> +	return ret;
> +}
> +
> +static int query_buffer(struct com_buf_info *info)
> +{
> +	union acpi_object *out_obj;
> +	struct pfru_device *pf_dev;
> +	acpi_handle handle;
> +	int i, ret = -EINVAL;
> +
> +	pf_dev = get_pfru_dev();
> +	handle = ACPI_HANDLE(pf_dev->dev);
> +
> +	out_obj = acpi_evaluate_dsm_typed(handle, &pf_dev->uuid,
> +					  pf_dev->rev_id, FUNC_QUERY_BUF,
> +					  NULL, ACPI_TYPE_PACKAGE);
> +	if (!out_obj)
> +		return -EINVAL;
> +
> +	for (i = 0; i < out_obj->package.count; i++) {
> +		union acpi_object *obj = &out_obj->package.elements[i];
> +
> +		switch (i) {
> +		case 0:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			info->status = obj->integer.value;
> +			break;
> +		case 1:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			info->ext_status = obj->integer.value;
> +			break;
> +		case 2:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			info->addr_lo = obj->integer.value;
> +			break;
> +		case 3:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			info->addr_hi = obj->integer.value;
> +			break;
> +		case 4:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			info->buf_size = obj->integer.value;
> +			break;

The same comments as for query_capability() functions apply here as well.

> +		}
> +	}
> +	ret = 0;
> +
> +free_acpi_buffer:
> +	ACPI_FREE(out_obj);
> +
> +	return ret;
> +}
> +
> +static int get_image_type(efi_manage_capsule_image_header_t *img_hdr,
> +			  int *type)
> +{
> +	int ret;
> +	guid_t code_inj_id, drv_update_id, *image_type_id;
> +
> +	ret = guid_parse(PFRU_CODE_INJ_UUID, &code_inj_id);
> +	if (ret)
> +		return ret;
> +	ret = guid_parse(PFRU_DRV_UPDATE_UUID, &drv_update_id);
> +	if (ret)
> +		return ret;

The code_inj_id and drv_update_id are "constant", they can be parsed once
at driver initialization time. 

> +	/* check whether this is a code injection or driver update */
> +	image_type_id = &img_hdr->image_type_id;
> +	if (guid_equal(image_type_id, &code_inj_id))
> +		*type = CODE_INJECT_TYPE;
> +	else if (guid_equal(image_type_id, &drv_update_id))
> +		*type = DRIVER_UPDATE_TYPE;
> +	else
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +/*
> + * The (u64 hw_ins) was introduced in UEFI spec version 2,
> + * and (u64 capsule_support) was introduced in version 3.
> + * The size needs to be adjusted accordingly.

The comment here does not really explain how exactly the size needs to be
adjusted.

> + */
> +static int adjust_efi_size(efi_manage_capsule_image_header_t *img_hdr,
> +			   int *size)
> +{
> +	int tmp_size = *size;
> +
> +	tmp_size += sizeof(efi_manage_capsule_image_header_t);
> +	switch (img_hdr->ver) {
> +	case 1:
> +		tmp_size -= 2 * sizeof(u64);
> +		break;
> +	case 2:
> +		tmp_size -= sizeof(u64);
> +		break;
> +	default:
> +		/* only support version 1 and 2 */
> +		return -EINVAL;
> +	}
> +	*size = tmp_size;
> +
> +	return 0;
> +}
> +
> +/*
> + * Sanity check if the capsule image has a newer version than current one.
> + * Return: true if it is valid, false otherwise.
> + */
> +static bool valid_version(const void *data, struct update_cap_info *cap)
> +{
> +	struct payload_hdr *payload_hdr;
> +	efi_capsule_header_t *cap_hdr;
> +	efi_manage_capsule_header_t *m_hdr;
> +	efi_manage_capsule_image_header_t *m_img_hdr;
> +	efi_image_auth_t *auth;
> +	int type, size, ret;
> +
> +	cap_hdr = (efi_capsule_header_t *)data;
> +	size = cap_hdr->headersize;
> +	m_hdr = (efi_manage_capsule_header_t *)(data + size);
> +	/*
> +	 * Current data structure size plus variable array indicated
> +	 * by number of (emb_drv_cnt + payload_cnt)
> +	 */
> +	size += sizeof(efi_manage_capsule_header_t) +
> +		      (m_hdr->emb_drv_cnt + m_hdr->payload_cnt) * sizeof(u64);
> +	m_img_hdr = (efi_manage_capsule_image_header_t *)(data + size);
> +
> +	ret = get_image_type(m_img_hdr, &type);
> +	if (ret)
> +		return false;
> +
> +	ret = adjust_efi_size(m_img_hdr, &size);
> +	if (ret)
> +		return false;
> +
> +	auth = (efi_image_auth_t *)(data + size);
> +	size += sizeof(u64) + auth->auth_info.hdr.len;
> +	payload_hdr = (struct payload_hdr *)(data + size);
> +
> +	/* Finally, compare the version. */
> +	if (type == CODE_INJECT_TYPE)
> +		return payload_hdr->rt_ver >= cap->code_rt_version;
> +	else
> +		return payload_hdr->rt_ver >= cap->drv_rt_version;
> +}
> +
> +static void parse_update_result(struct updated_result *result)

I think dump_update_result() would be more appropriate.

> +{
> +	pr_debug("Update result:\n");
> +	pr_debug("Status:%d\n", result->status);
> +	pr_debug("Extended Status:%d\n", result->ext_status);
> +	pr_debug("Authentication Time Low:%ld\n", result->low_auth_time);
> +	pr_debug("Authentication Time High:%ld\n", result->high_auth_time);
> +	pr_debug("Execution Time Low:%ld\n", result->low_exec_time);
> +	pr_debug("Execution Time High:%ld\n", result->high_exec_time);
> +}
> +
> +static int start_acpi_update(int action)
> +{
> +	union acpi_object *out_obj, in_obj, in_buf;
> +	struct updated_result update_result;
> +	acpi_handle handle;
> +	struct pfru_device *pf_dev;
> +	int i, ret = -EINVAL;
> +
> +	pf_dev = get_pfru_dev();
> +	memset(&in_obj, 0, sizeof(in_obj));
> +	memset(&in_buf, 0, sizeof(in_buf));
> +	in_obj.type = ACPI_TYPE_PACKAGE;
> +	in_obj.package.count = 1;
> +	in_obj.package.elements = &in_buf;
> +	in_buf.type = ACPI_TYPE_INTEGER;
> +	in_buf.integer.value = action;
> +
> +	handle = ACPI_HANDLE(pf_dev->dev);
> +	out_obj = acpi_evaluate_dsm_typed(handle, &pf_dev->uuid,
> +					  pf_dev->rev_id, FUNC_START,
> +					  &in_obj, ACPI_TYPE_PACKAGE);
> +	if (!out_obj)
> +		return -EINVAL;
> +
> +	for (i = 0; i < out_obj->package.count; i++) {
> +		union acpi_object *obj = &out_obj->package.elements[i];
> +
> +		switch (i) {
> +		case 0:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			update_result.status = obj->integer.value;
> +			break;
> +		case 1:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			update_result.ext_status = obj->integer.value;
> +			break;
> +		case 2:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			update_result.low_auth_time = obj->integer.value;
> +			break;
> +		case 3:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			update_result.high_auth_time = obj->integer.value;
> +			break;
> +		case 4:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			update_result.low_exec_time = obj->integer.value;
> +			break;
> +		case 5:
> +			if (obj->type != ACPI_TYPE_INTEGER)
> +				goto free_acpi_buffer;
> +			update_result.high_exec_time = obj->integer.value;
> +			break;
> +		}

The same comments as for query_capability() functions apply here as well.

> +	}
> +	parse_update_result(&update_result);
> +	ret = 0;
> +
> +free_acpi_buffer:
> +	ACPI_FREE(out_obj);
> +
> +	return ret;
> +}
> +
> +static long pfru_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +	void __user *p;
> +	int ret = 0, rev;
> +	struct pfru_device *pf_dev;
> +
> +	pf_dev = get_pfru_dev();
> +
> +	p = (void __user *)arg;
> +
> +	switch (cmd) {
> +	case PFRU_IOC_SET_REV:
> +		if (copy_from_user(&rev, p, sizeof(unsigned int)))
> +			return -EFAULT;
> +		if (!valid_revid(rev))
> +			return -EFAULT;
> +		pf_dev->rev_id = rev;
> +		break;
> +	case PFRU_IOC_STAGE:
> +		ret = start_acpi_update(START_STAGE);
> +		if (ret)
> +			return ret;
> +		break;
> +	case PFRU_IOC_ACTIVATE:
> +		ret = start_acpi_update(START_ACTIVATE);
> +		if (ret)
> +			return ret;
> +		break;
> +	case PFRU_IOC_STAGE_ACTIVATE:
> +		ret = start_acpi_update(START_STAGE_ACTIVATE);
> +		if (ret)
> +			return ret;

These 
	if (ret)
		return ret;

seem redundant here.

> +		break;
> +	default:
> +		ret = -ENOIOCTLCMD;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +#ifdef CONFIG_COMPAT
> +static long compat_pfru_ioctl(struct file *filep, unsigned int cmd,
> +			      unsigned long arg)
> +{
> +	return pfru_ioctl(filep, cmd, arg);
> +}
> +#endif
> +
> +static int pfru_open(struct inode *inode, struct file *file)
> +{
> +	return capable(CAP_SYS_RAWIO) ? stream_open(inode, file) : -EPERM;
> +}
> +
> +static ssize_t pfru_write(struct file *file, const char __user *buf,
> +			  size_t len, loff_t *ppos)
> +{
> +	struct update_cap_info cap;
> +	struct com_buf_info info;
> +	phys_addr_t phy_addr;
> +	struct iov_iter iter;
> +	struct iovec iov;
> +	char *buf_ptr;
> +	int ret;
> +
> +	ret = query_buffer(&info);
> +	if (ret)
> +		return ret;
> +
> +	if (len > info.buf_size)
> +		return -EINVAL;
> +
> +	iov.iov_base = (void __user *)buf;
> +	iov.iov_len = len;
> +	iov_iter_init(&iter, WRITE, &iov, 1, len);
> +
> +	/* map the communication buffer */
> +	phy_addr = (phys_addr_t)(info.addr_lo | (info.addr_hi << 32));
> +	buf_ptr = memremap(phy_addr, info.buf_size, MEMREMAP_WB);
> +	if (IS_ERR(buf_ptr))
> +		return PTR_ERR(buf_ptr);
> +	if (!copy_from_iter_full(buf_ptr, len, &iter)) {
> +		pr_err("error! could not read capsule file\n");
> +		ret = -EINVAL;
> +		goto unmap;
> +	}
> +
> +	/* Check if the capsule header has a valid version number. */
> +	ret = query_capability(&cap);
> +	if (ret)
> +		goto unmap;
> +
> +	if (cap.status != DSM_SUCCEED) {
> +		ret = -EBUSY;
> +		goto unmap;
> +	}
> +	if (!valid_version(buf_ptr, &cap)) {
> +		ret = -EINVAL;
> +		goto unmap;
> +	}
> +	ret = 0;
> +unmap:
> +	memunmap(buf_ptr);
> +
> +	return ret ?: len;
> +}
> +
> +static ssize_t pfru_read(struct file *filp, char __user *ubuf,
> +			 size_t size, loff_t *off)
> +{
> +	struct update_cap_info cap;
> +	int ret;
> +
> +	ret = query_capability(&cap);
> +	if (ret)
> +		return ret;
> +
> +	size = min_t(size_t, size, sizeof(cap));
> +
> +	if (copy_to_user(ubuf, &cap, size))
> +		return -EFAULT;
> +
> +	return size;
> +}
> +
> +static const struct file_operations acpi_pfru_fops = {
> +	.owner		= THIS_MODULE,
> +	.write		= pfru_write,
> +	.read		= pfru_read,
> +	.open		= pfru_open,
> +	.unlocked_ioctl = pfru_ioctl,
> +#ifdef CONFIG_COMPAT
> +	.compat_ioctl	= compat_pfru_ioctl,
> +#endif
> +	.llseek		= noop_llseek,
> +};
> +
> +static struct miscdevice pfru_misc_dev = {
> +	.minor = MISC_DYNAMIC_MINOR,
> +	.name = "pfru_update",
> +	.nodename = "pfru/update",
> +	.fops = &acpi_pfru_fops,
> +};
> +
> +static int acpi_pfru_remove(struct platform_device *pdev)
> +{
> +	misc_deregister(&pfru_misc_dev);
> +
> +	return 0;
> +}
> +
> +static int acpi_pfru_probe(struct platform_device *pdev)
> +{
> +	acpi_handle handle;
> +	struct pfru_device *pf_dev;
> +	int ret;
> +
> +	pf_dev = get_pfru_dev();
> +
> +	ret = guid_parse(PFRU_UUID, &pf_dev->uuid);
> +	if (ret)
> +		return ret;
> +	/* default rev id is 1 */
> +	pf_dev->rev_id = 1;
> +	pf_dev->dev = &pdev->dev;
> +	handle = ACPI_HANDLE(pf_dev->dev);
> +	if (!acpi_has_method(handle, "_DSM")) {
> +		pr_err("Missing _DSM\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = misc_register(&pfru_misc_dev);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct acpi_device_id acpi_pfru_ids[] = {
> +	{"INTC1080", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(acpi, acpi_pfru_ids);
> +
> +static struct platform_driver acpi_pfru_driver = {
> +	.driver = {
> +		.name = "pfru_update",
> +		.acpi_match_table = acpi_pfru_ids,
> +	},
> +	.probe = acpi_pfru_probe,
> +	.remove = acpi_pfru_remove,
> +};
> +module_platform_driver(acpi_pfru_driver);
> +
> +MODULE_DESCRIPTION("Platform Firmware Runtime Update device driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/uapi/linux/pfru.h b/include/uapi/linux/pfru.h
> new file mode 100644
> index 000000000000..81eb8ad5a57e
> --- /dev/null
> +++ b/include/uapi/linux/pfru.h
> @@ -0,0 +1,106 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Platform Firmware Runtime Update header
> + *
> + * Copyright(c) 2021 Intel Corporation. All rights reserved.
> + */
> +#ifndef __PFRU_H__
> +#define __PFRU_H__
> +
> +#include <linux/ioctl.h>
> +#include <linux/uuid.h>
> +
> +#define PFRU_UUID		"ECF9533B-4A3C-4E89-939E-C77112601C6D"
> +#define PFRU_CODE_INJ_UUID		"B2F84B79-7B6E-4E45-885F-3FB9BB185402"
> +#define PFRU_DRV_UPDATE_UUID		"4569DD8C-75F1-429A-A3D6-24DE8097A0DF"
> +
> +#define FUNC_STANDARD_QUERY	0
> +#define FUNC_QUERY_UPDATE_CAP	1
> +#define FUNC_QUERY_BUF		2
> +#define FUNC_START		3
> +
> +#define CODE_INJECT_TYPE	1
> +#define DRIVER_UPDATE_TYPE	2
> +
> +#define REVID_1		1
> +#define REVID_2		2
> +
> +#define PFRU_MAGIC 0xEE
> +
> +#define PFRU_IOC_SET_REV _IOW(PFRU_MAGIC, 0x01, unsigned int)
> +#define PFRU_IOC_STAGE _IOW(PFRU_MAGIC, 0x02, unsigned int)
> +#define PFRU_IOC_ACTIVATE _IOW(PFRU_MAGIC, 0x03, unsigned int)
> +#define PFRU_IOC_STAGE_ACTIVATE _IOW(PFRU_MAGIC, 0x04, unsigned int)
> +
> +static inline int valid_revid(int id)
> +{
> +	return (id == REVID_1) || (id == REVID_2);
> +}
> +
> +/* Capsule file payload header */
> +struct payload_hdr {
> +	__u32	sig;
> +	__u32	hdr_version;
> +	__u32	hdr_size;
> +	__u32	hw_ver;
> +	__u32	rt_ver;
> +	guid_t	platform_id;
> +};
> +
> +enum start_action {
> +	START_STAGE,
> +	START_ACTIVATE,
> +	START_STAGE_ACTIVATE,
> +};
> +
> +enum dsm_status {
> +	DSM_SUCCEED,
> +	DSM_FUNC_NOT_SUPPORT,
> +	DSM_INVAL_INPUT,
> +	DSM_HARDWARE_ERR,
> +	DSM_RETRY_SUGGESTED,
> +	DSM_UNKNOWN,
> +	DSM_FUNC_SPEC_ERR,
> +};
> +
> +struct update_cap_info {
> +	enum dsm_status status;
> +	int update_cap;
> +
> +	guid_t code_type;
> +	int fw_version;
> +	int code_rt_version;
> +
> +	guid_t drv_type;
> +	int drv_rt_version;
> +	int drv_svn;
> +
> +	guid_t platform_id;
> +	guid_t oem_id;
> +
> +	char oem_info[];
> +};
> +
> +struct com_buf_info {
> +	enum dsm_status status;
> +	enum dsm_status ext_status;
> +	unsigned long addr_lo;
> +	unsigned long addr_hi;
> +	int buf_size;
> +};
> +
> +struct capsulate_buf_info {
> +	unsigned long src;
> +	int size;
> +};
> +
> +struct updated_result {
> +	enum dsm_status status;
> +	enum dsm_status ext_status;
> +	unsigned long low_auth_time;
> +	unsigned long high_auth_time;
> +	unsigned long low_exec_time;
> +	unsigned long high_exec_time;
> +};

Are all these types need to be visible to userspace?
If not, please move the driver internal types to pfru_update.c.

> +
> +#endif /* __PFRU_H__ */
> -- 
> 2.25.1
> 

-- 
Sincerely yours,
Mike.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry
  2021-09-07 15:40 ` [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry Chen Yu
  2021-09-07 21:28   ` Shuah Khan
@ 2021-09-08  9:08   ` Mike Rapoport
  2021-09-14  7:08     ` Chen Yu
  1 sibling, 1 reply; 16+ messages in thread
From: Mike Rapoport @ 2021-09-08  9:08 UTC (permalink / raw)
  To: Chen Yu
  Cc: linux-acpi, linux-kernel, Rafael J. Wysocki, Len Brown,
	Dan Williams, Andy Shevchenko, Aubrey Li, Ashok Raj, Shuah Khan,
	linux-kselftest, Dou Shengnan

On Tue, Sep 07, 2021 at 11:40:30PM +0800, Chen Yu wrote:
> Introduce a simple test for Platform Firmware Runtime Update and Telemetry
> drivers. It is based on ioctl to either update firmware driver or code injection,
> and read corresponding PFRU Telemetry log into user space.
> 
> For example:
> 
> ./pfru_test -h
> usage: pfru_test [OPTIONS]
>  code injection:
>   -l, --load
>   -s, --stage
>   -a, --activate
>   -u, --update [stage and activate]
>   -q, --query
>   -d, --revid update
>  telemetry:
>   -G, --getloginfo
>   -T, --type(0:execution, 1:history)
>   -L, --level(0, 1, 2, 4)
>   -R, --read
>   -D, --revid log
> 
> ./pfru_test -G
>  log_level:4
>  log_type:0
>  log_revid:2
>  max_data_size:65536
>  chunk1_size:0
>  chunk2_size:1401
>  rollover_cnt:0
>  reset_cnt:4
> 
> ./pfru_test -q
>  code injection image type:794bf8b2-6e7b-454e-885f-3fb9bb185402
>  fw_version:0
>  code_rt_version:1
>  driver update image type:0e5f0b14-f849-7945-ad81-bc7b6d2bb245
>  drv_rt_version:0
>  drv_svn:0
>  platform id:39214663-b1a8-4eaa-9024-f2bb53ea4723
>  oem id:a36db54f-ea2a-e14e-b7c4-b5780e51ba3d
> 
> Tested-by: Dou Shengnan <shengnanx.dou@intel.com>
> Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> ---
>  tools/testing/selftests/Makefile         |   1 +
>  tools/testing/selftests/pfru/Makefile    |   7 +
>  tools/testing/selftests/pfru/config      |   2 +
>  tools/testing/selftests/pfru/pfru.h      | 152 +++++++++++
>  tools/testing/selftests/pfru/pfru_test.c | 324 +++++++++++++++++++++++
>  5 files changed, 486 insertions(+)
>  create mode 100644 tools/testing/selftests/pfru/Makefile
>  create mode 100644 tools/testing/selftests/pfru/config
>  create mode 100644 tools/testing/selftests/pfru/pfru.h
>  create mode 100644 tools/testing/selftests/pfru/pfru_test.c
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index fb010a35d61a..c8b53a2c4450 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -56,6 +56,7 @@ TARGETS += seccomp
>  TARGETS += sgx
>  TARGETS += sigaltstack
>  TARGETS += size
> +TARGETS += pfru
>  TARGETS += sparc64
>  TARGETS += splice
>  TARGETS += static_keys
> diff --git a/tools/testing/selftests/pfru/Makefile b/tools/testing/selftests/pfru/Makefile
> new file mode 100644
> index 000000000000..c61916ccf637
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +
> +CFLAGS += -Wall -O2
> +LDLIBS := -luuid
> +
> +TEST_GEN_PROGS := pfru_test
> +include ../lib.mk
> diff --git a/tools/testing/selftests/pfru/config b/tools/testing/selftests/pfru/config
> new file mode 100644
> index 000000000000..37f53609acbd
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/config
> @@ -0,0 +1,2 @@
> +CONFIG_ACPI_PFRU=m
> +CONFIG_ACPI_PFRU_TELEMETRY=m
> diff --git a/tools/testing/selftests/pfru/pfru.h b/tools/testing/selftests/pfru/pfru.h
> new file mode 100644
> index 000000000000..8cd4ed80b161
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/pfru.h
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Platform Firmware Runtime Update header
> + *
> + * Copyright(c) 2021 Intel Corporation. All rights reserved.
> + */
> +#ifndef __PFRU_H__
> +#define __PFRU_H__
> +
> +#include <linux/ioctl.h>
> +#include <uuid/uuid.h>
> +
> +#define PFRU_UUID		"ECF9533B-4A3C-4E89-939E-C77112601C6D"
> +#define PFRU_CODE_INJ_UUID		"B2F84B79-7B6E-4E45-885F-3FB9BB185402"
> +#define PFRU_DRV_UPDATE_UUID		"4569DD8C-75F1-429A-A3D6-24DE8097A0DF"
> +
> +#define FUNC_STANDARD_QUERY	0
> +#define FUNC_QUERY_UPDATE_CAP	1
> +#define FUNC_QUERY_BUF		2
> +#define FUNC_START		3
> +
> +#define CODE_INJECT_TYPE	1
> +#define DRIVER_UPDATE_TYPE	2
> +
> +#define REVID_1		1
> +#define REVID_2		2
> +
> +#define PFRU_MAGIC 0xEE
> +
> +#define PFRU_IOC_SET_REV _IOW(PFRU_MAGIC, 0x01, unsigned int)
> +#define PFRU_IOC_STAGE _IOW(PFRU_MAGIC, 0x02, unsigned int)
> +#define PFRU_IOC_ACTIVATE _IOW(PFRU_MAGIC, 0x03, unsigned int)
> +#define PFRU_IOC_STAGE_ACTIVATE _IOW(PFRU_MAGIC, 0x04, unsigned int)
> +
> +static inline int valid_revid(int id)
> +{
> +	return (id == REVID_1) || (id == REVID_2);
> +}
> +
> +/* Capsule file payload header */
> +struct payload_hdr {
> +	__u32	sig;
> +	__u32	hdr_version;
> +	__u32	hdr_size;
> +	__u32	hw_ver;
> +	__u32	rt_ver;
> +	uuid_t	platform_id;
> +};
> +
> +enum start_action {
> +	START_STAGE,
> +	START_ACTIVATE,
> +	START_STAGE_ACTIVATE,
> +};
> +
> +enum dsm_status {
> +	DSM_SUCCEED,
> +	DSM_FUNC_NOT_SUPPORT,
> +	DSM_INVAL_INPUT,
> +	DSM_HARDWARE_ERR,
> +	DSM_RETRY_SUGGESTED,
> +	DSM_UNKNOWN,
> +	DSM_FUNC_SPEC_ERR,
> +};
> +
> +struct update_cap_info {
> +	enum dsm_status status;
> +	int update_cap;
> +
> +	uuid_t code_type;
> +	int fw_version;
> +	int code_rt_version;
> +
> +	uuid_t drv_type;
> +	int drv_rt_version;
> +	int drv_svn;
> +
> +	uuid_t platform_id;
> +	uuid_t oem_id;
> +
> +	char oem_info[];
> +};
> +
> +struct com_buf_info {
> +	enum dsm_status status;
> +	enum dsm_status ext_status;
> +	unsigned long addr_lo;
> +	unsigned long addr_hi;
> +	int buf_size;
> +};
> +
> +struct capsulate_buf_info {
> +	unsigned long src;
> +	int size;
> +};
> +
> +struct updated_result {
> +	enum dsm_status status;
> +	enum dsm_status ext_status;
> +	unsigned long low_auth_time;
> +	unsigned long high_auth_time;
> +	unsigned long low_exec_time;
> +	unsigned long high_exec_time;
> +};

Most of these types and constants seem to be a copy of uapu/linux/pfru.h.
Shouldn't the test get them from there?

> +
> +#define PFRU_TELEMETRY_UUID	"75191659-8178-4D9D-B88F-AC5E5E93E8BF"
> +
> +/* Telemetry structures. */
> +struct telem_data_info {
> +	enum dsm_status status;
> +	enum dsm_status ext_status;
> +	/* Maximum supported size of data of
> +	 * all Data Chunks combined.
> +	 */
> +	unsigned long chunk1_addr_lo;
> +	unsigned long chunk1_addr_hi;
> +	unsigned long chunk2_addr_lo;
> +	unsigned long chunk2_addr_hi;
> +	int max_data_size;
> +	int chunk1_size;
> +	int chunk2_size;
> +	int rollover_cnt;
> +	int reset_cnt;
> +};
> +
> +struct telem_info {
> +	int log_level;
> +	int log_type;
> +	int log_revid;
> +};
> +
> +/* Two logs: history and execution log */
> +#define LOG_EXEC_IDX	0
> +#define LOG_HISTORY_IDX	1
> +#define NR_LOG_TYPE	2
> +
> +#define LOG_ERR		0
> +#define LOG_WARN	1
> +#define LOG_INFO	2
> +#define LOG_VERB	4
> +
> +#define FUNC_SET_LEV		1
> +#define FUNC_GET_LEV		2
> +#define FUNC_GET_DATA		3
> +
> +#define LOG_NAME_SIZE		10
> +
> +#define PFRU_LOG_IOC_SET_INFO _IOW(PFRU_MAGIC, 0x05, struct telem_info)
> +#define PFRU_LOG_IOC_GET_INFO _IOR(PFRU_MAGIC, 0x06, struct telem_info)
> +#define PFRU_LOG_IOC_GET_DATA_INFO _IOR(PFRU_MAGIC, 0x07, struct telem_data_info)
> +
> +#endif /* __PFRU_H__ */
> diff --git a/tools/testing/selftests/pfru/pfru_test.c b/tools/testing/selftests/pfru/pfru_test.c
> new file mode 100644
> index 000000000000..d24d79d3836e
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/pfru_test.c
> @@ -0,0 +1,324 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Tests Runtime Update/Telemetry (see Documentation/x86/pfru_update.rst)
> + */
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <getopt.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include "pfru.h"
> +
> +#define MAX_LOG_SIZE 65536
> +
> +struct update_cap_info cap_info;
> +struct com_buf_info buf_info;
> +struct capsulate_buf_info image_info;
> +struct telem_data_info data_info;
> +char *capsule_name;
> +int action, query_cap, log_type, log_level, log_read, log_getinfo,
> +	revid, log_revid;
> +int set_log_level, set_log_type,
> +	set_revid, set_log_revid;
> +
> +char *progname;
> +
> +static int valid_log_level(int level)
> +{
> +	return (level == LOG_ERR) || (level == LOG_WARN) ||
> +		(level == LOG_INFO) || (level == LOG_VERB);
> +}
> +
> +static int valid_log_type(int type)
> +{
> +	return (type == LOG_EXEC_IDX) || (type == LOG_HISTORY_IDX);
> +}
> +
> +static void help(void)
> +{
> +	fprintf(stderr,
> +		"usage: %s [OPTIONS]\n"
> +		" code injection:\n"
> +		"  -l, --load\n"
> +		"  -s, --stage\n"
> +		"  -a, --activate\n"
> +		"  -u, --update [stage and activate]\n"
> +		"  -q, --query\n"
> +		"  -d, --revid update\n"
> +		" telemetry:\n"
> +		"  -G, --getloginfo\n"
> +		"  -T, --type(0:execution, 1:history)\n"
> +		"  -L, --level(0, 1, 2, 4)\n"
> +		"  -R, --read\n"
> +		"  -D, --revid log\n",
> +		progname);
> +}
> +
> +char *option_string = "l:sauqd:GT:L:RD:h";
> +static struct option long_options[] = {
> +	{"load", required_argument, 0, 'l'},
> +	{"stage", no_argument, 0, 's'},
> +	{"activate", no_argument, 0, 'a'},
> +	{"update", no_argument, 0, 'u'},
> +	{"query", no_argument, 0, 'q'},
> +	{"getloginfo", no_argument, 0, 'G'},
> +	{"type", required_argument, 0, 'T'},
> +	{"level", required_argument, 0, 'L'},
> +	{"read", no_argument, 0, 'R'},
> +	{"setrev", required_argument, 0, 'd'},
> +	{"setrevlog", required_argument, 0, 'D'},
> +	{"help", no_argument, 0, 'h'},
> +	{}
> +};
> +
> +static void parse_options(int argc, char **argv)
> +{
> +	char *pathname;
> +	int c;
> +
> +	pathname = strdup(argv[0]);
> +	progname = basename(pathname);
> +
> +	while (1) {
> +		int option_index = 0;
> +
> +		c = getopt_long(argc, argv, option_string,
> +				long_options, &option_index);
> +		if (c == -1)
> +			break;
> +		switch (c) {
> +		case 'l':
> +			capsule_name = optarg;
> +			break;
> +		case 's':
> +			action = 1;
> +			break;
> +		case 'a':
> +			action = 2;
> +			break;
> +		case 'u':
> +			action = 3;
> +			break;
> +		case 'q':
> +			query_cap = 1;
> +			break;
> +		case 'G':
> +			log_getinfo = 1;
> +			break;
> +		case 'T':
> +			log_type = atoi(optarg);
> +			set_log_type = 1;
> +			break;
> +		case 'L':
> +			log_level = atoi(optarg);
> +			set_log_level = 1;
> +			break;
> +		case 'R':
> +			log_read = 1;
> +			break;
> +		case 'd':
> +			revid = atoi(optarg);
> +			set_revid = 1;
> +			break;
> +		case 'D':
> +			log_revid = atoi(optarg);
> +			set_log_revid = 1;
> +			break;
> +		case 'h':
> +			help();
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +}
> +
> +void print_cap(struct update_cap_info *cap)
> +{
> +	char *uuid = malloc(37);
> +
> +	if (!uuid) {
> +		perror("Can not allocate uuid buffer\n");
> +		exit(1);
> +	}
> +	uuid_unparse(cap->code_type, uuid);
> +	printf("code injection image type:%s\n", uuid);
> +	printf("fw_version:%d\n", cap->fw_version);
> +	printf("code_rt_version:%d\n", cap->code_rt_version);
> +
> +	uuid_unparse(cap->drv_type, uuid);
> +	printf("driver update image type:%s\n", uuid);
> +	printf("drv_rt_version:%d\n", cap->drv_rt_version);
> +	printf("drv_svn:%d\n", cap->drv_svn);
> +
> +	uuid_unparse(cap->platform_id, uuid);
> +	printf("platform id:%s\n", uuid);
> +	uuid_unparse(cap->oem_id, uuid);
> +	printf("oem id:%s\n", uuid);
> +
> +	free(uuid);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int fd_update, fd_log, fd_capsule;
> +	struct telem_data_info data_info;
> +	struct telem_info info;
> +	struct update_cap_info cap;
> +	void *addr_map_capsule;
> +	struct stat st;
> +	char *log_buf;
> +	int ret = 0;
> +
> +	parse_options(argc, argv);
> +
> +	fd_log = open("/dev/pfru/telemetry", O_RDWR);
> +	if (fd_log < 0) {
> +		perror("Cannot open telemetry device...");
> +		return 1;
> +	}
> +	fd_update = open("/dev/pfru/update", O_RDWR);
> +	if (fd_update < 0) {
> +		perror("Cannot open code injection device...");
> +		return 1;
> +	}
> +
> +	if (query_cap) {
> +		ret = read(fd_update, &cap, sizeof(cap));
> +		if (ret == -1) {
> +			perror("Read error.");
> +			return 1;
> +		}
> +		print_cap(&cap);
> +	}
> +
> +	if (log_getinfo) {
> +		ret = ioctl(fd_log, PFRU_LOG_IOC_GET_DATA_INFO, &data_info);
> +		if (ret) {
> +			perror("Get log data info failed.");
> +			return 1;
> +		}
> +		ret = ioctl(fd_log, PFRU_LOG_IOC_GET_INFO, &info);
> +		if (ret) {
> +			perror("Get log info failed.");
> +			return 1;
> +		}
> +		printf("log_level:%d\n", info.log_level);
> +		printf("log_type:%d\n", info.log_type);
> +		printf("log_revid:%d\n", info.log_revid);
> +		printf("max_data_size:%d\n", data_info.max_data_size);
> +		printf("chunk1_size:%d\n", data_info.chunk1_size);
> +		printf("chunk2_size:%d\n", data_info.chunk2_size);
> +		printf("rollover_cnt:%d\n", data_info.rollover_cnt);
> +		printf("reset_cnt:%d\n", data_info.reset_cnt);
> +
> +		return 0;
> +	}
> +
> +	info.log_level = -1;
> +	info.log_type = -1;
> +	info.log_revid = -1;
> +
> +	if (set_log_level) {
> +		if (!valid_log_level(log_level)) {
> +			printf("Invalid log level %d\n",
> +			       log_level);
> +		} else {
> +			info.log_level = log_level;
> +		}
> +	}
> +	if (set_log_type) {
> +		if (!valid_log_type(log_type)) {
> +			printf("Invalid log type %d\n",
> +			       log_type);
> +		} else {
> +			info.log_type = log_type;
> +		}
> +	}
> +	if (set_log_revid) {
> +		if (!valid_revid(log_revid)) {
> +			printf("Invalid log revid %d\n",
> +			       log_revid);
> +		} else {
> +			info.log_revid = log_revid;
> +		}
> +	}
> +
> +	ret = ioctl(fd_log, PFRU_LOG_IOC_SET_INFO, &info);
> +	if (ret) {
> +		perror("Log information set failed.(log_level, log_type, log_revid)");
> +		return 1;
> +	}
> +
> +	if (set_revid) {
> +		ret = ioctl(fd_update, PFRU_IOC_SET_REV, &revid);
> +		if (ret) {
> +			perror("mru update revid set failed");
> +			return 1;
> +		}
> +		printf("mru update revid set to %d\n", revid);
> +	}
> +
> +	if (capsule_name) {
> +		fd_capsule = open(capsule_name, O_RDONLY);
> +		if (fd_capsule < 0) {
> +			perror("Can not open capsule file...");
> +			return 1;
> +		}
> +		if (fstat(fd_capsule, &st) < 0) {
> +			perror("Can not fstat capsule file...");
> +			return 1;
> +		}
> +		addr_map_capsule = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
> +					fd_capsule, 0);
> +		if (addr_map_capsule == MAP_FAILED) {
> +			perror("Failed to mmap capsule file.");
> +			return 1;
> +		}
> +		ret = write(fd_update, (char *)addr_map_capsule, st.st_size);
> +		printf("Load %d bytes of capsule file into the system\n",
> +		       ret);
> +		if (ret == -1) {
> +			perror("Failed to load capsule file");
> +			return 1;
> +		}
> +		munmap(addr_map_capsule, st.st_size);
> +		printf("Load done.\n");
> +	}
> +
> +	if (action) {
> +		if (action == 1)
> +			ret = ioctl(fd_update, PFRU_IOC_STAGE, NULL);
> +		else if (action == 2)
> +			ret = ioctl(fd_update, PFRU_IOC_ACTIVATE, NULL);
> +		else if (action == 3)
> +			ret = ioctl(fd_update, PFRU_IOC_STAGE_ACTIVATE, NULL);
> +		else
> +			return 1;
> +		printf("Update finished, return %d\n", ret);
> +	}
> +
> +	if (log_read) {
> +		log_buf = malloc(MAX_LOG_SIZE + 1);
> +		if (!log_buf) {
> +			perror("log_buf allocate failed.");
> +			return 1;
> +		}
> +		ret = read(fd_log, log_buf, MAX_LOG_SIZE);
> +		if (ret == -1) {
> +			perror("Read error.");
> +			return 1;
> +		}
> +		log_buf[ret] = '\0';
> +		printf("%s\n", log_buf);
> +		free(log_buf);
> +	}
> +
> +	return 0;
> +}
> -- 
> 2.25.1
> 

-- 
Sincerely yours,
Mike.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver
  2021-09-08  9:04   ` Mike Rapoport
@ 2021-09-14  5:54     ` Chen Yu
  0 siblings, 0 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-14  5:54 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: linux-acpi, linux-kernel, Rafael J. Wysocki, Len Brown,
	Dan Williams, Andy Shevchenko, Aubrey Li, Ashok Raj,
	Jonathan Corbet, Greg Kroah-Hartman, Hans de Goede,
	Maximilian Luz, Alexander Graf, Jarkko Sakkinen, Shuo Liu,
	Hannes Reinecke, Ioana Ciornei, Jiri Slaby, Andra Paraschiv,
	Randy Dunlap, Ben Widawsky, linux-doc

Thank you very much and sorry for late response, Mike,
On Wed, Sep 08, 2021 at 12:04:24PM +0300, Mike Rapoport wrote:
> Hi,
> 
> On Tue, Sep 07, 2021 at 11:27:42PM +0800, Chen Yu wrote:
> > Introduce the pfru_update driver which can be used for Platform Firmware
> > Runtime code injection and driver update. The user is expected to provide
> > the update firmware in the form of capsule file, and pass it to the driver
> > via ioctl. Then the driver would hand this capsule file to the Platform
> > Firmware Runtime Update via the ACPI device _DSM method. At last the low
> > level Management Mode would do the firmware update.
> > 
> > Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> > ---
> >  .../userspace-api/ioctl/ioctl-number.rst      |   1 +
> >  drivers/acpi/Kconfig                          |   1 +
> >  drivers/acpi/Makefile                         |   1 +
> >  drivers/acpi/pfru/Kconfig                     |  15 +
> >  drivers/acpi/pfru/Makefile                    |   2 +
> >  drivers/acpi/pfru/pfru_update.c               | 544 ++++++++++++++++++
> >  include/uapi/linux/pfru.h                     | 106 ++++
> >  7 files changed, 670 insertions(+)
[cut]
> > +struct pfru_device {
> > +	guid_t uuid;
> > +	int rev_id;
> > +	struct device *dev;
> > +};
> > +
> > +static struct pfru_device pfru_dev;
> 
> Please don't presume a single instance and update the driver to work with
> allocated pfru_device objects.
> 
Okay, this global variable has been switched to a dynamic allocated pointer.
Since there would be only one instance of this ACPI device, more than one
instance would indicate a bogus BIOS. The duplication check logic was added
during driver initialization that, if more than than one instance was detected,
only the first instance would take effect.
> > +static struct pfru_device *get_pfru_dev(void)
> > +{
> > +	return &pfru_dev;
> > +}
> > +
[cut]
> > +	for (i = 0; i < out_obj->package.count; i++) {
> > +		union acpi_object *obj = &out_obj->package.elements[i];
> > +
> > +		switch (i) {
> > +		case 0:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			cap->status = obj->integer.value;
> > +			break;
> > +		case 1:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			cap->update_cap = obj->integer.value;
> > +			break;
> > +		case 2:
> > +			if (obj->type != ACPI_TYPE_BUFFER)
> > +				goto free_acpi_buffer;
> > +			memcpy(&cap->code_type, obj->buffer.pointer,
> > +			       obj->buffer.length);
> > +			break;
> > +		case 3:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			cap->fw_version = obj->integer.value;
> > +			break;
> > +		case 4:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			cap->code_rt_version = obj->integer.value;
> > +			break;
> > +		case 5:
> > +			if (obj->type != ACPI_TYPE_BUFFER)
> > +				goto free_acpi_buffer;
> > +			memcpy(&cap->drv_type, obj->buffer.pointer,
> > +			       obj->buffer.length);
> > +			break;
> > +		case 6:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			cap->drv_rt_version = obj->integer.value;
> > +			break;
> > +		case 7:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			cap->drv_svn = obj->integer.value;
> > +			break;
> > +		case 8:
> > +			if (obj->type != ACPI_TYPE_BUFFER)
> > +				goto free_acpi_buffer;
> > +			memcpy(&cap->platform_id, obj->buffer.pointer,
> > +			       obj->buffer.length);
> > +			break;
> > +		case 9:
> > +			if (obj->type != ACPI_TYPE_BUFFER)
> > +				goto free_acpi_buffer;
> > +			memcpy(&cap->oem_id, obj->buffer.pointer,
> > +			       obj->buffer.length);
> > +			break;
> 
> Please get rid of the magic numbers and add a default clause with the error
> handling. Nothing guaranties that there will be exactly 10 fields in the
> out_obj.
> 
Okay, got it, changed in next version.
> Also, the obj->type checks could move outside the switch, with an if or a
> static mapping between the field number and the desired type.
> 
> 
Okay, changed in next version.
> > +		}
> > +	}
[cut]
> > +		case 4:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			info->buf_size = obj->integer.value;
> > +			break;
> 
> The same comments as for query_capability() functions apply here as well.
> 
Okay.
> > +		}
> > +	}
> > +	ret = 0;
> > +
> > +free_acpi_buffer:
> > +	ACPI_FREE(out_obj);
> > +
> > +	return ret;
> > +}
> > +
> > +static int get_image_type(efi_manage_capsule_image_header_t *img_hdr,
> > +			  int *type)
> > +{
> > +	int ret;
> > +	guid_t code_inj_id, drv_update_id, *image_type_id;
> > +
> > +	ret = guid_parse(PFRU_CODE_INJ_UUID, &code_inj_id);
> > +	if (ret)
> > +		return ret;
> > +	ret = guid_parse(PFRU_DRV_UPDATE_UUID, &drv_update_id);
> > +	if (ret)
> > +		return ret;
> 
> The code_inj_id and drv_update_id are "constant", they can be parsed once
> at driver initialization time. 
> 
Okay, changed in next version.
> > +	/* check whether this is a code injection or driver update */
> > +	image_type_id = &img_hdr->image_type_id;
> > +	if (guid_equal(image_type_id, &code_inj_id))
> > +		*type = CODE_INJECT_TYPE;
> > +	else if (guid_equal(image_type_id, &drv_update_id))
> > +		*type = DRIVER_UPDATE_TYPE;
> > +	else
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * The (u64 hw_ins) was introduced in UEFI spec version 2,
> > + * and (u64 capsule_support) was introduced in version 3.
> > + * The size needs to be adjusted accordingly.
> 
> The comment here does not really explain how exactly the size needs to be
> adjusted.
> 
The comment has been refined in next version.
> > +
[cut]
> > +static void parse_update_result(struct updated_result *result)
> 
> I think dump_update_result() would be more appropriate.
> 
Okay, changed accordingly.
> > +{
> > +	pr_debug("Update result:\n");
> > +	pr_debug("Status:%d\n", result->status);
> > +	pr_debug("Extended Status:%d\n", result->ext_status);
> > +	pr_debug("Authentication Time Low:%ld\n", result->low_auth_time);
> > +	pr_debug("Authentication Time High:%ld\n", result->high_auth_time);
> > +	pr_debug("Execution Time Low:%ld\n", result->low_exec_time);
> > +	pr_debug("Execution Time High:%ld\n", result->high_exec_time);
> > +}
> > +
> > +		case 5:
> > +			if (obj->type != ACPI_TYPE_INTEGER)
> > +				goto free_acpi_buffer;
> > +			update_result.high_exec_time = obj->integer.value;
> > +			break;
> > +		}
> 
> The same comments as for query_capability() functions apply here as well.
>
Okay, changed in next version. 
> > +	}
> > +	parse_update_result(&update_result);
> > +	ret = 0;
> > +
> > +free_acpi_buffer:
> > +	ACPI_FREE(out_obj);
> > +
> > +	return ret;
> > +}
> > +
[cut]
> > +	case PFRU_IOC_STAGE_ACTIVATE:
> > +		ret = start_acpi_update(START_STAGE_ACTIVATE);
> > +		if (ret)
> > +			return ret;
> 
> These 
> 	if (ret)
> 		return ret;
> 
> seem redundant here.
> 
I see. Changed accordingly.
> > +		break;
> > +	default:
> > +		ret = -ENOIOCTLCMD;
> > +		break;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +
[cut]
> > +struct com_buf_info {
> > +	enum dsm_status status;
> > +	enum dsm_status ext_status;
> > +	unsigned long addr_lo;
> > +	unsigned long addr_hi;
> > +	int buf_size;
> > +};
> > +
> > +struct capsulate_buf_info {
> > +	unsigned long src;
> > +	int size;
> > +};
> > +
> > +struct updated_result {
> > +	enum dsm_status status;
> > +	enum dsm_status ext_status;
> > +	unsigned long low_auth_time;
> > +	unsigned long high_auth_time;
> > +	unsigned long low_exec_time;
> > +	unsigned long high_exec_time;
> > +};
> 
> Are all these types need to be visible to userspace?
> If not, please move the driver internal types to pfru_update.c.
> 
The struct capsulate_buf_info is only usable in kernel space, so it
is moved into pfru_update.c in next version. While the other structures
are required by the userspace for customization.

thanks,
Chenyu

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry
  2021-09-07 21:28   ` Shuah Khan
@ 2021-09-14  6:46     ` Chen Yu
  0 siblings, 0 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-14  6:46 UTC (permalink / raw)
  To: Shuah Khan
  Cc: linux-acpi, linux-kernel, Rafael J. Wysocki, Len Brown,
	Dan Williams, Andy Shevchenko, Aubrey Li, Ashok Raj, Shuah Khan,
	linux-kselftest, Dou Shengnan

Hi Shuah, thank you for taking a look at this,
On Tue, Sep 07, 2021 at 03:28:52PM -0600, Shuah Khan wrote:
> On 9/7/21 9:40 AM, Chen Yu wrote:
> > Introduce a simple test for Platform Firmware Runtime Update and Telemetry
> > drivers. It is based on ioctl to either update firmware driver or code injection,
> > and read corresponding PFRU Telemetry log into user space.
> > 
> 
> A few things to consider and add handling for them in the
> test.
> 
> What happens when non-root user runs this test?
Currently the code does not distinguish between root and non-root. The
next version will terminate if the user is non-root.
> What happens when the pfru device doesn't exist?
> 
Currently the code terminates if either pfru_update or pfru_telemetry
device was not found.
> 
> [snip]
> 
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +	int fd_update, fd_log, fd_capsule;
> > +	struct telem_data_info data_info;
> > +	struct telem_info info;
> > +	struct update_cap_info cap;
> > +	void *addr_map_capsule;
> > +	struct stat st;
> > +	char *log_buf;
> > +	int ret = 0;
> > +
> > +	parse_options(argc, argv);
> > +
> > +	fd_log = open("/dev/pfru/telemetry", O_RDWR);
> > +	if (fd_log < 0) {
> > +		perror("Cannot open telemetry device...");
> > +		return 1;
> > +	}
> 
> Is this considered an error or unsupported?
> 
> > +	fd_update = open("/dev/pfru/update", O_RDWR);
> > +	if (fd_update < 0) {
> > +		perror("Cannot open code injection device...");
> > +		return 1;
> > +	}
> > +
> 
> Same here. If test is run on platform with pfru test should skip
> instead of reporting failure/error.
> 
Okay, got it. The next version will do the following to fix this:
1. If the pfru_update device is not found, the test will terminate.
   This is because the pfru_update driver is the fundamental driver.
   If this driver is not detected, there would be no information at all.
2. If the pfru_telemetry device is not found, the test will skip
   the log setting/retrieving. Since the pfru_telemetry driver
   is optional, the user can still update the firmware without
   checking the telemetry log.

Thanks,
Chenyu 

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry
  2021-09-08  9:08   ` Mike Rapoport
@ 2021-09-14  7:08     ` Chen Yu
  0 siblings, 0 replies; 16+ messages in thread
From: Chen Yu @ 2021-09-14  7:08 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: linux-acpi, linux-kernel, Rafael J. Wysocki, Len Brown,
	Dan Williams, Andy Shevchenko, Aubrey Li, Ashok Raj, Shuah Khan,
	linux-kselftest, Dou Shengnan

Hi Mike,
On Wed, Sep 08, 2021 at 12:08:40PM +0300, Mike Rapoport wrote:
> On Tue, Sep 07, 2021 at 11:40:30PM +0800, Chen Yu wrote:
> > Introduce a simple test for Platform Firmware Runtime Update and Telemetry
> > drivers. It is based on ioctl to either update firmware driver or code injection,
> > and read corresponding PFRU Telemetry log into user space.
> >
[snip.. 
> > +struct updated_result {
> > +	enum dsm_status status;
> > +	enum dsm_status ext_status;
> > +	unsigned long low_auth_time;
> > +	unsigned long high_auth_time;
> > +	unsigned long low_exec_time;
> > +	unsigned long high_exec_time;
> > +};
> 
> Most of these types and constants seem to be a copy of uapu/linux/pfru.h.
> Shouldn't the test get them from there?
>
Yes they have shared structures. The next version will reuse the uapi header.

thanks,
Chenyu 

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2021-09-14  7:03 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-07 15:08 [PATCH 0/5][RFC] Introduce Platform Firmware Runtime Update and Telemetry drivers Chen Yu
2021-09-07 15:15 ` [PATCH 1/5][RFC] Documentation: Introduce Platform Firmware Runtime Update documentation Chen Yu
2021-09-07 15:23   ` Jonathan Corbet
2021-09-07 15:48     ` Chen Yu
2021-09-07 15:17 ` [PATCH 2/5][RFC] efi: Introduce EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER and corresponding structures Chen Yu
2021-09-07 16:06   ` Ard Biesheuvel
2021-09-07 23:56     ` Chen Yu
2021-09-07 15:27 ` [PATCH 3/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update device driver Chen Yu
2021-09-08  9:04   ` Mike Rapoport
2021-09-14  5:54     ` Chen Yu
2021-09-07 15:39 ` [PATCH 4/5][RFC] drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry Chen Yu
2021-09-07 15:40 ` [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry Chen Yu
2021-09-07 21:28   ` Shuah Khan
2021-09-14  6:46     ` Chen Yu
2021-09-08  9:08   ` Mike Rapoport
2021-09-14  7:08     ` Chen Yu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).