Re: [PATCH v3 4/5] drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Sep 16, 2021 at 5:58 PM Chen Yu <yu.c.chen@xxxxxxxxx> wrote:
>
> 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@xxxxxxxxx>
> ---
> v3: Use __u32 instead of int and __64 instead of unsigned long
>     in include/uapi/linux/pfru.h (Greg Kroah-Hartman)
>     Rename the structure in uapi to start with a prefix pfru so as
>     to avoid confusing in the global namespace. (Greg Kroah-Hartman)
> v2: Do similar clean up as pfru_update driver:
>     Add sanity check for duplicated instance of ACPI device.
>     Update the driver to work with allocated telem_device objects.
>     (Mike Rapoport)
>     For each switch case pair, get rid of the magic case numbers
>     and add a default clause with the error handling.
>     (Mike Rapoport)
> ---
>  drivers/acpi/pfru/Kconfig          |  14 +
>  drivers/acpi/pfru/Makefile         |   1 +
>  drivers/acpi/pfru/pfru_telemetry.c | 412 +++++++++++++++++++++++++++++
>  include/uapi/linux/pfru.h          |  43 +++
>  4 files changed, 470 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

Why does this need a separate Kconfig option?

> +       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

I'm not sure about this ...

> diff --git a/drivers/acpi/pfru/pfru_telemetry.c b/drivers/acpi/pfru/pfru_telemetry.c
> new file mode 100644
> index 000000000000..96dc9e69edc0
> --- /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@xxxxxxxxx>
> + */
> +#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>
> +
> +enum update_index {
> +       LOG_STATUS_IDX,
> +       LOG_EXT_STATUS_IDX,
> +       LOG_MAX_SZ_IDX,
> +       LOG_CHUNK1_LO_IDX,
> +       LOG_CHUNK1_HI_IDX,
> +       LOG_CHUNK1_SZ_IDX,
> +       LOG_CHUNK2_LO_IDX,
> +       LOG_CHUNK2_HI_IDX,
> +       LOG_CHUNK2_SZ_IDX,
> +       LOG_ROLLOVER_CNT_IDX,
> +       LOG_RESET_CNT_IDX,
> +};
> +
> +struct pfru_telem_device {
> +       struct device *dev;
> +       guid_t uuid;
> +       struct pfru_telem_info info;
> +};
> +
> +/*
> + * There would be only one instance of pfru_telem_device.
> + */
> +static struct pfru_telem_device *telem_dev;
> +
> +static int get_pfru_data_info(struct pfru_telem_data_info *data_info,
> +                             int log_type)
> +{
> +       union acpi_object *out_obj, in_obj, in_buf;
> +       acpi_handle handle;
> +       int i, ret = -EINVAL;
> +
> +       handle = ACPI_HANDLE(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, &telem_dev->uuid,
> +                                         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++) {

Is the loop really necessary?

> +               union acpi_object *obj = &out_obj->package.elements[i];
> +
> +               if (obj->type != ACPI_TYPE_INTEGER)
> +                       goto free_acpi_buffer;
> +
> +               switch (i) {
> +               case LOG_STATUS_IDX:
> +                       data_info->status = obj->integer.value;
> +                       break;
> +               case LOG_EXT_STATUS_IDX:
> +                       data_info->ext_status = obj->integer.value;
> +                       break;
> +               case LOG_MAX_SZ_IDX:
> +                       data_info->max_data_size = obj->integer.value;
> +                       break;
> +               case LOG_CHUNK1_LO_IDX:
> +                       data_info->chunk1_addr_lo = obj->integer.value;
> +                       break;
> +               case LOG_CHUNK1_HI_IDX:
> +                       data_info->chunk1_addr_hi = obj->integer.value;
> +                       break;
> +               case LOG_CHUNK1_SZ_IDX:
> +                       data_info->chunk1_size = obj->integer.value;
> +                       break;
> +               case LOG_CHUNK2_LO_IDX:
> +                       data_info->chunk2_addr_lo = obj->integer.value;
> +                       break;
> +               case LOG_CHUNK2_HI_IDX:
> +                       data_info->chunk2_addr_hi = obj->integer.value;
> +                       break;
> +               case LOG_CHUNK2_SZ_IDX:
> +                       data_info->chunk2_size = obj->integer.value;
> +                       break;
> +               case LOG_ROLLOVER_CNT_IDX:
> +                       data_info->rollover_cnt = obj->integer.value;
> +                       break;
> +               case LOG_RESET_CNT_IDX:
> +                       data_info->reset_cnt = obj->integer.value;
> +                       break;
> +               default:
> +                       pr_err("Incorrect format of Telemetry info.\n");

dev_dbg()?  And likewise below.

> +                       goto free_acpi_buffer;
> +               }
> +       }
> +       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;
> +       enum pfru_dsm_status status;
> +       acpi_handle handle;
> +       int ret = -EINVAL;
> +
> +       handle = ACPI_HANDLE(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, &telem_dev->uuid,
> +                                         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)
> +{
> +       union acpi_object *out_obj, *obj;
> +       enum pfru_dsm_status status;
> +       acpi_handle handle;
> +       int ret = -EINVAL;
> +
> +       handle = ACPI_HANDLE(telem_dev->dev);
> +       out_obj = acpi_evaluate_dsm_typed(handle, &telem_dev->uuid,
> +                                         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);

The parens are redundant AFAICS.

> +}
> +
> +static int valid_log_type(int type)
> +{
> +       return (type == LOG_EXEC_IDX) || (type == LOG_HISTORY_IDX);

And here too.

> +}
> +
> +static long pfru_telemetry_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +
> +{
> +       struct pfru_telem_data_info data_info;
> +       struct pfru_telem_info info;
> +       void __user *p;
> +       int ret = 0;
> +
> +       p = (void __user *)arg;
> +
> +       switch (cmd) {
> +       case PFRU_LOG_IOC_SET_INFO:
> +               if (copy_from_user(&info, p, sizeof(info)))
> +                       return -EFAULT;

Empty line, please.

> +               if (pfru_valid_revid(info.log_revid))
> +                       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;

And here.

> +                       telem_dev->info.log_level = info.log_level;
> +               }
> +               if (valid_log_type(info.log_type))
> +                       telem_dev->info.log_type = info.log_type;

And here.  And analogously below.

Generally speaking, I tend to prefer putting empty code lines after an
if () statement that is not followed by a block.

> +               break;
> +       case PFRU_LOG_IOC_GET_INFO:
> +               ret = get_pfru_log_level(&info.log_level);
> +               if (ret)
> +                       return ret;
> +               info.log_type = telem_dev->info.log_type;
> +               info.log_revid = 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, telem_dev->info.log_type);
> +               if (ret)
> +                       return ret;
> +               if (copy_to_user(p, &data_info, sizeof(struct pfru_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_data_info info;
> +       phys_addr_t base_addr;
> +       int buf_size, ret;
> +       char *buf_ptr;
> +
> +       if (*off < 0)
> +               return -EINVAL;
> +
> +       ret = get_pfru_data_info(&info, 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));

This line doesn't need to be broken as far as I'm concerned.

> +       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;

So this would be far more readable in the following form IMO:

size = min_t(size_t, size, buf_size - *off);
if (copy_to_user(ubuf, buf_ptr + *off, size))
        ret = -EFAULT;
else
        ret = 0;

And it looks like you won't need the "out" label any more then.

> +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,
> +};

And I would consider combining this with the update interface so that
the write() updates the firmware code and the read() gets telemetry
data from it.

> +
> +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);
> +       kfree(telem_dev);
> +       telem_dev = NULL;
> +
> +       return 0;
> +}
> +
> +static int acpi_pfru_telemetry_probe(struct platform_device *pdev)
> +{
> +       acpi_handle handle;
> +       int ret;
> +
> +       if (telem_dev) {
> +               pr_err("Duplicated PFRU Telemetry INTC1081 detected, skip...\n");
> +               return 0;
> +       }
> +
> +       telem_dev = kzalloc(sizeof(*telem_dev), GFP_KERNEL);
> +       if (!telem_dev)
> +               return -ENOMEM;
> +
> +       ret = guid_parse(PFRU_TELEMETRY_UUID, &telem_dev->uuid);
> +       if (ret)
> +               goto out;
> +
> +       telem_dev->info.log_revid = 1;
> +       telem_dev->dev = &pdev->dev;
> +       handle = ACPI_HANDLE(telem_dev->dev);
> +       if (!acpi_has_method(handle, "_DSM")) {
> +               pr_err("Missing _DSM\n");
> +               ret = -ENODEV;
> +               goto out;
> +       }
> +
> +       ret = misc_register(&pfru_telemetry_misc_dev);
> +       if (ret)
> +               goto out;
> +
> +       return 0;
> +out:
> +       kfree(telem_dev);
> +       telem_dev = NULL;
> +
> +       return ret;
> +}
> +
> +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 ca0b7433f79f..b04852602133 100644
> --- a/include/uapi/linux/pfru.h
> +++ b/include/uapi/linux/pfru.h
> @@ -98,4 +98,47 @@ struct pfru_updated_result {
>         __u64 high_exec_time;
>  };
>
> +#define PFRU_TELEMETRY_UUID    "75191659-8178-4D9D-B88F-AC5E5E93E8BF"
> +
> +/* Telemetry structures. */
> +struct pfru_telem_data_info {
> +       enum pfru_dsm_status status;
> +       enum pfru_dsm_status ext_status;
> +       __u64 chunk1_addr_lo;
> +       __u64 chunk1_addr_hi;
> +       __u64 chunk2_addr_lo;
> +       __u64 chunk2_addr_hi;
> +       __u32 max_data_size;
> +       __u32 chunk1_size;
> +       __u32 chunk2_size;
> +       __u32 rollover_cnt;
> +       __u32 reset_cnt;
> +};
> +
> +struct pfru_telem_info {
> +       __u32 log_level;
> +       __u32 log_type;
> +       __u32 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 pfru_telem_info)
> +#define PFRU_LOG_IOC_GET_INFO _IOR(PFRU_MAGIC, 0x06, struct pfru_telem_info)
> +#define PFRU_LOG_IOC_GET_DATA_INFO _IOR(PFRU_MAGIC, 0x07, struct pfru_telem_data_info)
> +
>  #endif /* __PFRU_H__ */
> --



[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux