diff mbox series

[v4,3/4] drivers/acpi: Introduce Platform Firmware Runtime Update Telemetry

Message ID 838245e376c7e6fd0fe1ef55d004ed53763846a2.1634310710.git.yu.c.chen@intel.com (mailing list archive)
State Superseded, archived
Headers show
Series Introduce Platform Firmware Runtime Update and Telemetry drivers | expand

Commit Message

Chen Yu Oct. 16, 2021, 10:44 a.m. UTC
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.

Also add the ABI documentation.

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>
---
v4: Remove the telemetry kernel Kconfig and combine it with pfru
    (Rafael J. Wysocki)
    Remove redundant parens. (Rafael J. Wysocki)
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/pfru_update.c | 380 +++++++++++++++++++++++++++++++-
 include/uapi/linux/pfru.h       |  43 ++++
 2 files changed, 421 insertions(+), 2 deletions(-)

Comments

Greg KH Oct. 16, 2021, 3:10 p.m. UTC | #1
On Sat, Oct 16, 2021 at 06:44:31PM +0800, Chen Yu 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.

What type of interface is this?  How does userspace interact with it?

> 
> Also add the ABI documentation.

Add it where?

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

This log does not make any sense to me, where is it from?  Why the odd
line-wrapping?

> 
> Signed-off-by: Chen Yu <yu.c.chen@intel.com>
> ---
> v4: Remove the telemetry kernel Kconfig and combine it with pfru
>     (Rafael J. Wysocki)
>     Remove redundant parens. (Rafael J. Wysocki)
> 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/pfru_update.c | 380 +++++++++++++++++++++++++++++++-
>  include/uapi/linux/pfru.h       |  43 ++++
>  2 files changed, 421 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/acpi/pfru/pfru_update.c b/drivers/acpi/pfru/pfru_update.c
> index f57a39e79808..fe5b1bf0aeb3 100644
> --- a/drivers/acpi/pfru/pfru_update.c
> +++ b/drivers/acpi/pfru/pfru_update.c
> @@ -55,6 +55,27 @@ enum update_index {
>  	UPDATE_NR_IDX,
>  };
>  
> +enum log_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,
> +	LOG_NR_IDX,
> +};
> +
> +struct pfru_log_device {
> +	struct device *dev;
> +	guid_t uuid;
> +	struct pfru_log_info info;
> +};
> +
>  struct pfru_device {
>  	guid_t uuid, code_uuid, drv_uuid;
>  	int rev_id;
> @@ -62,6 +83,299 @@ struct pfru_device {
>  };
>  
>  static struct pfru_device *pfru_dev;
> +static struct pfru_log_device *pfru_log_dev;
> +
> +static int get_pfru_log_data_info(struct pfru_log_data_info *data_info,
> +				  int log_type)
> +{
> +	union acpi_object *out_obj, in_obj, in_buf;
> +	acpi_handle handle;
> +	int ret = -EINVAL;
> +
> +	handle = ACPI_HANDLE(pfru_log_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, &pfru_log_dev->uuid,
> +					  pfru_log_dev->info.log_revid, FUNC_GET_DATA,
> +					  &in_obj, ACPI_TYPE_PACKAGE);
> +	if (!out_obj)
> +		return ret;
> +
> +	if (out_obj->package.count < LOG_NR_IDX)
> +		goto free_acpi_buffer;
> +
> +	if (out_obj->package.elements[LOG_STATUS_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->status = out_obj->package.elements[LOG_STATUS_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_EXT_STATUS_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->ext_status =
> +		out_obj->package.elements[LOG_EXT_STATUS_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_MAX_SZ_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->max_data_size =
> +		out_obj->package.elements[LOG_MAX_SZ_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_CHUNK1_LO_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->chunk1_addr_lo =
> +		out_obj->package.elements[LOG_CHUNK1_LO_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_CHUNK1_HI_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->chunk1_addr_hi =
> +		out_obj->package.elements[LOG_CHUNK1_HI_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_CHUNK1_SZ_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->chunk1_size =
> +		out_obj->package.elements[LOG_CHUNK1_SZ_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_CHUNK2_LO_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->chunk2_addr_lo =
> +		out_obj->package.elements[LOG_CHUNK2_LO_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_CHUNK2_HI_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->chunk2_addr_hi =
> +		out_obj->package.elements[LOG_CHUNK2_HI_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_CHUNK2_SZ_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->chunk2_size =
> +		out_obj->package.elements[LOG_CHUNK2_SZ_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->rollover_cnt =
> +		out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].integer.value;
> +
> +	if (out_obj->package.elements[LOG_RESET_CNT_IDX].type != ACPI_TYPE_INTEGER)
> +		goto free_acpi_buffer;
> +
> +	data_info->reset_cnt =
> +		out_obj->package.elements[LOG_RESET_CNT_IDX].integer.value;
> +
> +	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(pfru_log_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, &pfru_log_dev->uuid,
> +					  pfru_log_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)
> +		goto free_acpi_buffer;
> +
> +	obj = &out_obj->package.elements[1];
> +	status = obj->integer.value;
> +	if (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(pfru_log_dev->dev);
> +	out_obj = acpi_evaluate_dsm_typed(handle, &pfru_log_dev->uuid,
> +					  pfru_log_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)
> +		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)
> +		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;
> +}
> +
> +long pfru_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> +	struct pfru_log_data_info data_info;
> +	struct pfru_log_info info;
> +	void __user *p;
> +	int ret = 0;
> +
> +	if (!pfru_log_dev)
> +		return -ENODEV;
> +
> +	p = (void __user *)arg;
> +
> +	switch (cmd) {
> +	case PFRU_LOG_IOC_SET_INFO:
> +		if (copy_from_user(&info, p, sizeof(info)))
> +			return -EFAULT;
> +
> +		if (pfru_valid_revid(info.log_revid))
> +			pfru_log_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;
> +			pfru_log_dev->info.log_level = info.log_level;
> +		}
> +
> +		if (valid_log_type(info.log_type))
> +			pfru_log_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 = pfru_log_dev->info.log_type;
> +		info.log_revid = pfru_log_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_log_data_info(&data_info, pfru_log_dev->info.log_type);
> +		if (ret)
> +			return ret;
> +
> +		if (copy_to_user(p, &data_info, sizeof(struct pfru_log_data_info)))
> +			ret = -EFAULT;
> +
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +		break;
> +	}
> +	return ret;
> +}
> +
> +ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
> +		      size_t size, loff_t *off)
> +{
> +	struct pfru_log_data_info info;
> +	phys_addr_t base_addr;
> +	int buf_size, ret;
> +	char *buf_ptr;
> +
> +	if (!pfru_log_dev)
> +		return -ENODEV;
> +
> +	if (*off < 0)
> +		return -EINVAL;
> +
> +	ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
> +	if (ret)
> +		return ret;
> +
> +	base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
> +	/* pfru update has not been launched yet.*/
> +	if (!base_addr)
> +		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);
> +	if (copy_to_user(ubuf, buf_ptr + *off, size))
> +		ret = -EFAULT;
> +	else
> +		ret = 0;

As all you are doing is mapping some memory and reading from it, why do
you need a read() file operation at all?  Why not just use mmap?

thanks,

greg k-h
Greg KH Oct. 20, 2021, 8:27 a.m. UTC | #2
On Wed, Oct 20, 2021 at 04:29:39PM +0800, Chen Yu wrote:
> > > +ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
> > > +		      size_t size, loff_t *off)
> > > +{
> > > +	struct pfru_log_data_info info;
> > > +	phys_addr_t base_addr;
> > > +	int buf_size, ret;
> > > +	char *buf_ptr;
> > > +
> > > +	if (!pfru_log_dev)
> > > +		return -ENODEV;
> > > +
> > > +	if (*off < 0)
> > > +		return -EINVAL;
> > > +
> > > +	ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
> > > +	/* pfru update has not been launched yet.*/
> > > +	if (!base_addr)
> > > +		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);
> > > +	if (copy_to_user(ubuf, buf_ptr + *off, size))
> > > +		ret = -EFAULT;
> > > +	else
> > > +		ret = 0;
> > 
> > As all you are doing is mapping some memory and reading from it, why do
> > you need a read() file operation at all?  Why not just use mmap?
> > 
> In the beginning mmap() interface was provided to the user. Then it was
> realized that there is no guarantee in the spec that, the physical address
> provided by the BIOS would remain unchanged. So instead of asking the user
> to mmap the file each time before reading the log, the read() is leveraged
> here to always memremap() the latest address.

So you are forced to memremap on _EVERY_ read call because the BIOS can
change things underneath us without the kernel knowing about it?  How
does the chunk2_addr_lo and _hi values change while the system is
running?  Where does that happen, and isn't this going to be a very slow
and expensive read call all the time?

thanks,

greg k-h
Chen Yu Oct. 20, 2021, 8:29 a.m. UTC | #3
On Sat, Oct 16, 2021 at 05:10:25PM +0200, Greg Kroah-Hartman wrote:
> On Sat, Oct 16, 2021 at 06:44:31PM +0800, Chen Yu 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.
> 
> What type of interface is this?  How does userspace interact with it?
> 
> > 
> > Also add the ABI documentation.
> 
> Add it where?
>
They are all ioctl interfaces, and was introduced in previous patch in
Documentation/ABI/testing/pfru. The way userspace interace with it is
introduced in next patch in the man page. I'll revise the commit log to
better describe how user could use it.
> > 
> > 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
> 
> This log does not make any sense to me, where is it from?  Why the odd
> line-wrapping?
>
It is from the telemetry log generated by the BIOS. Since this content is
platform specific, I'll remove the log in next version.
> > +};
> > +
> > +
[snip...]
> > +ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
> > +		      size_t size, loff_t *off)
> > +{
> > +	struct pfru_log_data_info info;
> > +	phys_addr_t base_addr;
> > +	int buf_size, ret;
> > +	char *buf_ptr;
> > +
> > +	if (!pfru_log_dev)
> > +		return -ENODEV;
> > +
> > +	if (*off < 0)
> > +		return -EINVAL;
> > +
> > +	ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
> > +	if (ret)
> > +		return ret;
> > +
> > +	base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
> > +	/* pfru update has not been launched yet.*/
> > +	if (!base_addr)
> > +		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);
> > +	if (copy_to_user(ubuf, buf_ptr + *off, size))
> > +		ret = -EFAULT;
> > +	else
> > +		ret = 0;
> 
> As all you are doing is mapping some memory and reading from it, why do
> you need a read() file operation at all?  Why not just use mmap?
> 
In the beginning mmap() interface was provided to the user. Then it was
realized that there is no guarantee in the spec that, the physical address
provided by the BIOS would remain unchanged. So instead of asking the user
to mmap the file each time before reading the log, the read() is leveraged
here to always memremap() the latest address.

thanks,
Chenyu
> thanks,
> 
> greg k-h
Mike Rapoport Oct. 20, 2021, 8:53 a.m. UTC | #4
On Wed, Oct 20, 2021 at 10:27:28AM +0200, Greg Kroah-Hartman wrote:
> On Wed, Oct 20, 2021 at 04:29:39PM +0800, Chen Yu wrote:
> > > > +ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
> > > > +		      size_t size, loff_t *off)
> > > > +{
> > > > +	struct pfru_log_data_info info;
> > > > +	phys_addr_t base_addr;
> > > > +	int buf_size, ret;
> > > > +	char *buf_ptr;
> > > > +
> > > > +	if (!pfru_log_dev)
> > > > +		return -ENODEV;
> > > > +
> > > > +	if (*off < 0)
> > > > +		return -EINVAL;
> > > > +
> > > > +	ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
> > > > +	/* pfru update has not been launched yet.*/
> > > > +	if (!base_addr)
> > > > +		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);
> > > > +	if (copy_to_user(ubuf, buf_ptr + *off, size))
> > > > +		ret = -EFAULT;
> > > > +	else
> > > > +		ret = 0;
> > > 
> > > As all you are doing is mapping some memory and reading from it, why do
> > > you need a read() file operation at all?  Why not just use mmap?
> > > 
> > In the beginning mmap() interface was provided to the user. Then it was
> > realized that there is no guarantee in the spec that, the physical address
> > provided by the BIOS would remain unchanged. So instead of asking the user
> > to mmap the file each time before reading the log, the read() is leveraged
> > here to always memremap() the latest address.
> 
> So you are forced to memremap on _EVERY_ read call because the BIOS can
> change things underneath us without the kernel knowing about it?  How
> does the chunk2_addr_lo and _hi values change while the system is
> running?  Where does that happen, and isn't this going to be a very slow
> and expensive read call all the time?

And maybe it is something that can be fixed in the spec so that the address
will remain constant?

(I realise it's wishful thinking, but sill...)
Chen Yu Oct. 20, 2021, 9:17 a.m. UTC | #5
On Wed, Oct 20, 2021 at 10:27:28AM +0200, Greg Kroah-Hartman wrote:
> On Wed, Oct 20, 2021 at 04:29:39PM +0800, Chen Yu wrote:
> > > > +ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
> > > > +		      size_t size, loff_t *off)
> > > > +{
> > > > +	struct pfru_log_data_info info;
> > > > +	phys_addr_t base_addr;
> > > > +	int buf_size, ret;
> > > > +	char *buf_ptr;
> > > > +
> > > > +	if (!pfru_log_dev)
> > > > +		return -ENODEV;
> > > > +
> > > > +	if (*off < 0)
> > > > +		return -EINVAL;
> > > > +
> > > > +	ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
> > > > +	/* pfru update has not been launched yet.*/
> > > > +	if (!base_addr)
> > > > +		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);
> > > > +	if (copy_to_user(ubuf, buf_ptr + *off, size))
> > > > +		ret = -EFAULT;
> > > > +	else
> > > > +		ret = 0;
> > > 
> > > As all you are doing is mapping some memory and reading from it, why do
> > > you need a read() file operation at all?  Why not just use mmap?
> > > 
> > In the beginning mmap() interface was provided to the user. Then it was
> > realized that there is no guarantee in the spec that, the physical address
> > provided by the BIOS would remain unchanged. So instead of asking the user
> > to mmap the file each time before reading the log, the read() is leveraged
> > here to always memremap() the latest address.
> 
> So you are forced to memremap on _EVERY_ read call because the BIOS can
> change things underneath us without the kernel knowing about it?  How
> does the chunk2_addr_lo and _hi values change while the system is
> running?  Where does that happen, and isn't this going to be a very slow
> and expensive read call all the time?
>
It was not documented in the spec whether the chunk address will change or not,
for safety I changed it from mmap() to read(). I'll try to reach the spec designer
and check if the address will change or not.

thanks,
Chenyu
 
> thanks,
> 
> greg k-h
kernel test robot Oct. 21, 2021, 6:37 a.m. UTC | #6
Hi Chen,

I love your patch! Perhaps something to improve:

[auto build test WARNING on rafael-pm/linux-next]
[also build test WARNING on efi/next linus/master v5.15-rc6 next-20211020]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Chen-Yu/Introduce-Platform-Firmware-Runtime-Update-and-Telemetry-drivers/20211016-184349
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
config: x86_64-randconfig-c007-20211021 (attached as .config)
compiler: clang version 14.0.0 (https://github.com/llvm/llvm-project 3cea2505fd8d99a9ba0cb625aecfe28a47c4e3f8)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/93acf331ef196b860f6b34a5e9f8b3a249f1a0ce
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Chen-Yu/Introduce-Platform-Firmware-Runtime-Update-and-Telemetry-drivers/20211016-184349
        git checkout 93acf331ef196b860f6b34a5e9f8b3a249f1a0ce
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/acpi/pfru/pfru_update.c:280:6: warning: no previous prototype for function 'pfru_log_ioctl' [-Wmissing-prototypes]
   long pfru_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        ^
   drivers/acpi/pfru/pfru_update.c:280:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   long pfru_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
   ^
   static 
>> drivers/acpi/pfru/pfru_update.c:338:9: warning: no previous prototype for function 'pfru_log_read' [-Wmissing-prototypes]
   ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
           ^
   drivers/acpi/pfru/pfru_update.c:338:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
   ^
   static 
   2 warnings generated.


vim +/pfru_log_ioctl +280 drivers/acpi/pfru/pfru_update.c

   279	
 > 280	long pfru_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
   281	{
   282		struct pfru_log_data_info data_info;
   283		struct pfru_log_info info;
   284		void __user *p;
   285		int ret = 0;
   286	
   287		if (!pfru_log_dev)
   288			return -ENODEV;
   289	
   290		p = (void __user *)arg;
   291	
   292		switch (cmd) {
   293		case PFRU_LOG_IOC_SET_INFO:
   294			if (copy_from_user(&info, p, sizeof(info)))
   295				return -EFAULT;
   296	
   297			if (pfru_valid_revid(info.log_revid))
   298				pfru_log_dev->info.log_revid = info.log_revid;
   299	
   300			if (valid_log_level(info.log_level)) {
   301				ret = set_pfru_log_level(info.log_level);
   302				if (ret)
   303					return ret;
   304				pfru_log_dev->info.log_level = info.log_level;
   305			}
   306	
   307			if (valid_log_type(info.log_type))
   308				pfru_log_dev->info.log_type = info.log_type;
   309	
   310			break;
   311		case PFRU_LOG_IOC_GET_INFO:
   312			ret = get_pfru_log_level(&info.log_level);
   313			if (ret)
   314				return ret;
   315	
   316			info.log_type = pfru_log_dev->info.log_type;
   317			info.log_revid = pfru_log_dev->info.log_revid;
   318			if (copy_to_user(p, &info, sizeof(info)))
   319				ret = -EFAULT;
   320	
   321			break;
   322		case PFRU_LOG_IOC_GET_DATA_INFO:
   323			ret = get_pfru_log_data_info(&data_info, pfru_log_dev->info.log_type);
   324			if (ret)
   325				return ret;
   326	
   327			if (copy_to_user(p, &data_info, sizeof(struct pfru_log_data_info)))
   328				ret = -EFAULT;
   329	
   330			break;
   331		default:
   332			ret = -ENOTTY;
   333			break;
   334		}
   335		return ret;
   336	}
   337	
 > 338	ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
   339			      size_t size, loff_t *off)
   340	{
   341		struct pfru_log_data_info info;
   342		phys_addr_t base_addr;
   343		int buf_size, ret;
   344		char *buf_ptr;
   345	
   346		if (!pfru_log_dev)
   347			return -ENODEV;
   348	
   349		if (*off < 0)
   350			return -EINVAL;
   351	
   352		ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
   353		if (ret)
   354			return ret;
   355	
   356		base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
   357		/* pfru update has not been launched yet.*/
   358		if (!base_addr)
   359			return -EBUSY;
   360	
   361		buf_size = info.max_data_size;
   362		if (*off >= buf_size)
   363			return 0;
   364	
   365		buf_ptr = memremap(base_addr, buf_size, MEMREMAP_WB);
   366		if (IS_ERR(buf_ptr))
   367			return PTR_ERR(buf_ptr);
   368	
   369		size = min_t(size_t, size, buf_size - *off);
   370		if (copy_to_user(ubuf, buf_ptr + *off, size))
   371			ret = -EFAULT;
   372		else
   373			ret = 0;
   374	
   375		memunmap(buf_ptr);
   376	
   377		return ret ? ret : size;
   378	}
   379	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/drivers/acpi/pfru/pfru_update.c b/drivers/acpi/pfru/pfru_update.c
index f57a39e79808..fe5b1bf0aeb3 100644
--- a/drivers/acpi/pfru/pfru_update.c
+++ b/drivers/acpi/pfru/pfru_update.c
@@ -55,6 +55,27 @@  enum update_index {
 	UPDATE_NR_IDX,
 };
 
+enum log_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,
+	LOG_NR_IDX,
+};
+
+struct pfru_log_device {
+	struct device *dev;
+	guid_t uuid;
+	struct pfru_log_info info;
+};
+
 struct pfru_device {
 	guid_t uuid, code_uuid, drv_uuid;
 	int rev_id;
@@ -62,6 +83,299 @@  struct pfru_device {
 };
 
 static struct pfru_device *pfru_dev;
+static struct pfru_log_device *pfru_log_dev;
+
+static int get_pfru_log_data_info(struct pfru_log_data_info *data_info,
+				  int log_type)
+{
+	union acpi_object *out_obj, in_obj, in_buf;
+	acpi_handle handle;
+	int ret = -EINVAL;
+
+	handle = ACPI_HANDLE(pfru_log_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, &pfru_log_dev->uuid,
+					  pfru_log_dev->info.log_revid, FUNC_GET_DATA,
+					  &in_obj, ACPI_TYPE_PACKAGE);
+	if (!out_obj)
+		return ret;
+
+	if (out_obj->package.count < LOG_NR_IDX)
+		goto free_acpi_buffer;
+
+	if (out_obj->package.elements[LOG_STATUS_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->status = out_obj->package.elements[LOG_STATUS_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_EXT_STATUS_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->ext_status =
+		out_obj->package.elements[LOG_EXT_STATUS_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_MAX_SZ_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->max_data_size =
+		out_obj->package.elements[LOG_MAX_SZ_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_CHUNK1_LO_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->chunk1_addr_lo =
+		out_obj->package.elements[LOG_CHUNK1_LO_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_CHUNK1_HI_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->chunk1_addr_hi =
+		out_obj->package.elements[LOG_CHUNK1_HI_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_CHUNK1_SZ_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->chunk1_size =
+		out_obj->package.elements[LOG_CHUNK1_SZ_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_CHUNK2_LO_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->chunk2_addr_lo =
+		out_obj->package.elements[LOG_CHUNK2_LO_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_CHUNK2_HI_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->chunk2_addr_hi =
+		out_obj->package.elements[LOG_CHUNK2_HI_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_CHUNK2_SZ_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->chunk2_size =
+		out_obj->package.elements[LOG_CHUNK2_SZ_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->rollover_cnt =
+		out_obj->package.elements[LOG_ROLLOVER_CNT_IDX].integer.value;
+
+	if (out_obj->package.elements[LOG_RESET_CNT_IDX].type != ACPI_TYPE_INTEGER)
+		goto free_acpi_buffer;
+
+	data_info->reset_cnt =
+		out_obj->package.elements[LOG_RESET_CNT_IDX].integer.value;
+
+	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(pfru_log_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, &pfru_log_dev->uuid,
+					  pfru_log_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)
+		goto free_acpi_buffer;
+
+	obj = &out_obj->package.elements[1];
+	status = obj->integer.value;
+	if (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(pfru_log_dev->dev);
+	out_obj = acpi_evaluate_dsm_typed(handle, &pfru_log_dev->uuid,
+					  pfru_log_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)
+		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)
+		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;
+}
+
+long pfru_log_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct pfru_log_data_info data_info;
+	struct pfru_log_info info;
+	void __user *p;
+	int ret = 0;
+
+	if (!pfru_log_dev)
+		return -ENODEV;
+
+	p = (void __user *)arg;
+
+	switch (cmd) {
+	case PFRU_LOG_IOC_SET_INFO:
+		if (copy_from_user(&info, p, sizeof(info)))
+			return -EFAULT;
+
+		if (pfru_valid_revid(info.log_revid))
+			pfru_log_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;
+			pfru_log_dev->info.log_level = info.log_level;
+		}
+
+		if (valid_log_type(info.log_type))
+			pfru_log_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 = pfru_log_dev->info.log_type;
+		info.log_revid = pfru_log_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_log_data_info(&data_info, pfru_log_dev->info.log_type);
+		if (ret)
+			return ret;
+
+		if (copy_to_user(p, &data_info, sizeof(struct pfru_log_data_info)))
+			ret = -EFAULT;
+
+		break;
+	default:
+		ret = -ENOTTY;
+		break;
+	}
+	return ret;
+}
+
+ssize_t pfru_log_read(struct file *filp, char __user *ubuf,
+		      size_t size, loff_t *off)
+{
+	struct pfru_log_data_info info;
+	phys_addr_t base_addr;
+	int buf_size, ret;
+	char *buf_ptr;
+
+	if (!pfru_log_dev)
+		return -ENODEV;
+
+	if (*off < 0)
+		return -EINVAL;
+
+	ret = get_pfru_log_data_info(&info, pfru_log_dev->info.log_type);
+	if (ret)
+		return ret;
+
+	base_addr = (phys_addr_t)(info.chunk2_addr_lo | (info.chunk2_addr_hi << 32));
+	/* pfru update has not been launched yet.*/
+	if (!base_addr)
+		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);
+	if (copy_to_user(ubuf, buf_ptr + *off, size))
+		ret = -EFAULT;
+	else
+		ret = 0;
+
+	memunmap(buf_ptr);
+
+	return ret ? ret : size;
+}
 
 static int query_capability(struct pfru_update_cap_info *cap)
 {
@@ -406,7 +720,7 @@  static long pfru_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 		ret = start_acpi_update(START_STAGE_ACTIVATE);
 		break;
 	default:
-		ret = -ENOTTY;
+		ret = pfru_log_ioctl(file, cmd, arg);
 		break;
 	}
 
@@ -467,6 +781,7 @@  static ssize_t pfru_write(struct file *file, const char __user *buf,
 static const struct file_operations acpi_pfru_fops = {
 	.owner		= THIS_MODULE,
 	.write		= pfru_write,
+	.read		= pfru_log_read,
 	.unlocked_ioctl = pfru_ioctl,
 	.llseek		= noop_llseek,
 };
@@ -478,6 +793,44 @@  static struct miscdevice pfru_misc_dev = {
 	.fops = &acpi_pfru_fops,
 };
 
+static int acpi_pfru_log_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int acpi_pfru_log_probe(struct platform_device *pdev)
+{
+	acpi_handle handle;
+	int ret;
+
+	/* One instance is expected. */
+	if (pfru_log_dev)
+		return 0;
+
+	pfru_log_dev = kzalloc(sizeof(*pfru_log_dev), GFP_KERNEL);
+	if (!pfru_log_dev)
+		return -ENOMEM;
+
+	ret = guid_parse(PFRU_LOG_UUID, &pfru_log_dev->uuid);
+	if (ret)
+		goto out;
+
+	pfru_log_dev->info.log_revid = 1;
+	pfru_log_dev->dev = &pdev->dev;
+	handle = ACPI_HANDLE(pfru_log_dev->dev);
+	if (!acpi_has_method(handle, "_DSM")) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	return 0;
+out:
+	kfree(pfru_log_dev);
+	pfru_log_dev = NULL;
+
+	return ret;
+}
+
 static int acpi_pfru_remove(struct platform_device *pdev)
 {
 	return 0;
@@ -526,6 +879,21 @@  static int acpi_pfru_probe(struct platform_device *pdev)
 	return ret;
 }
 
+static const struct acpi_device_id acpi_pfru_log_ids[] = {
+	{"INTC1081", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, acpi_pfru_log_ids);
+
+static struct platform_driver acpi_pfru_log_driver = {
+	.driver = {
+		.name = "pfru_log",
+		.acpi_match_table = acpi_pfru_log_ids,
+	},
+	.probe = acpi_pfru_log_probe,
+	.remove = acpi_pfru_log_remove,
+};
+
 static const struct acpi_device_id acpi_pfru_ids[] = {
 	{"INTC1080", 0},
 	{}
@@ -549,7 +917,11 @@  static int __init pfru_init(void)
 	if (ret)
 		return ret;
 
-	return platform_driver_register(&acpi_pfru_driver);
+	ret = platform_driver_register(&acpi_pfru_driver);
+	if (ret)
+		misc_deregister(&pfru_misc_dev);
+
+	return platform_driver_register(&acpi_pfru_log_driver);
 }
 
 static void __exit pfru_exit(void)
@@ -558,6 +930,10 @@  static void __exit pfru_exit(void)
 	misc_deregister(&pfru_misc_dev);
 	kfree(pfru_dev);
 	pfru_dev = NULL;
+
+	platform_driver_unregister(&acpi_pfru_log_driver);
+	kfree(pfru_log_dev);
+	pfru_log_dev = NULL;
 }
 
 module_init(pfru_init);
diff --git a/include/uapi/linux/pfru.h b/include/uapi/linux/pfru.h
index 127fc38638cb..9ab74d9cd21a 100644
--- a/include/uapi/linux/pfru.h
+++ b/include/uapi/linux/pfru.h
@@ -99,4 +99,47 @@  struct pfru_updated_result {
 	__u64 high_exec_time;
 };
 
+#define PFRU_LOG_UUID	"75191659-8178-4D9D-B88F-AC5E5E93E8BF"
+
+/* Telemetry structures. */
+struct pfru_log_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_log_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_log_info)
+#define PFRU_LOG_IOC_GET_INFO _IOR(PFRU_MAGIC, 0x06, struct pfru_log_info)
+#define PFRU_LOG_IOC_GET_DATA_INFO _IOR(PFRU_MAGIC, 0x07, struct pfru_log_data_info)
+
 #endif /* __PFRU_H__ */