2021-12-22 07:32:02 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* ACPI Platform Firmware Runtime Telemetry driver
*
* Copyright ( C ) 2021 Intel Corporation
* Author : Chen Yu < yu . c . chen @ intel . com >
*
* This driver allows user space to fetch telemetry data from the
* firmware with the help of the Platform Firmware Runtime Telemetry
* interface .
*/
# include <linux/acpi.h>
# include <linux/device.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/mm.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/pfrut.h>
# define PFRT_LOG_EXEC_IDX 0
# define PFRT_LOG_HISTORY_IDX 1
# define PFRT_LOG_ERR 0
# define PFRT_LOG_WARN 1
# define PFRT_LOG_INFO 2
# define PFRT_LOG_VERB 4
# define PFRT_FUNC_SET_LEV 1
# define PFRT_FUNC_GET_LEV 2
# define PFRT_FUNC_GET_DATA 3
# define PFRT_REVID_1 1
# define PFRT_REVID_2 2
# define PFRT_DEFAULT_REV_ID PFRT_REVID_1
enum log_index {
LOG_STATUS_IDX = 0 ,
LOG_EXT_STATUS_IDX = 1 ,
LOG_MAX_SZ_IDX = 2 ,
LOG_CHUNK1_LO_IDX = 3 ,
LOG_CHUNK1_HI_IDX = 4 ,
LOG_CHUNK1_SZ_IDX = 5 ,
LOG_CHUNK2_LO_IDX = 6 ,
LOG_CHUNK2_HI_IDX = 7 ,
LOG_CHUNK2_SZ_IDX = 8 ,
LOG_ROLLOVER_CNT_IDX = 9 ,
LOG_RESET_CNT_IDX = 10 ,
LOG_NR_IDX
} ;
struct pfrt_log_device {
int index ;
struct pfrt_log_info info ;
struct device * parent_dev ;
struct miscdevice miscdev ;
} ;
/* pfrt_guid is the parameter for _DSM method */
static const guid_t pfrt_log_guid =
GUID_INIT ( 0x75191659 , 0x8178 , 0x4D9D , 0xB8 , 0x8F , 0xAC , 0x5E ,
0x5E , 0x93 , 0xE8 , 0xBF ) ;
static DEFINE_IDA ( pfrt_log_ida ) ;
static inline struct pfrt_log_device * to_pfrt_log_dev ( struct file * file )
{
return container_of ( file - > private_data , struct pfrt_log_device , miscdev ) ;
}
static int get_pfrt_log_data_info ( struct pfrt_log_data_info * data_info ,
struct pfrt_log_device * pfrt_log_dev )
{
acpi_handle handle = ACPI_HANDLE ( pfrt_log_dev - > parent_dev ) ;
union acpi_object * out_obj , in_obj , in_buf ;
int ret = - EBUSY ;
2022-01-07 10:34:07 +03:00
memset ( data_info , 0 , sizeof ( * data_info ) ) ;
2021-12-22 07:32:02 +03:00
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 = pfrt_log_dev - > info . log_type ;
out_obj = acpi_evaluate_dsm_typed ( handle , & pfrt_log_guid ,
pfrt_log_dev - > info . log_revid , PFRT_FUNC_GET_DATA ,
& in_obj , ACPI_TYPE_PACKAGE ) ;
if ( ! out_obj )
return - EINVAL ;
if ( out_obj - > package . count < LOG_NR_IDX | |
out_obj - > package . elements [ LOG_STATUS_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_EXT_STATUS_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_MAX_SZ_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_CHUNK1_LO_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_CHUNK1_HI_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_CHUNK1_SZ_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_CHUNK2_LO_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_CHUNK2_HI_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_CHUNK2_SZ_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_ROLLOVER_CNT_IDX ] . type ! = ACPI_TYPE_INTEGER | |
out_obj - > package . elements [ LOG_RESET_CNT_IDX ] . type ! = ACPI_TYPE_INTEGER )
goto free_acpi_buffer ;
data_info - > status = out_obj - > package . elements [ LOG_STATUS_IDX ] . integer . value ;
data_info - > ext_status =
out_obj - > package . elements [ LOG_EXT_STATUS_IDX ] . integer . value ;
if ( data_info - > status ! = DSM_SUCCEED ) {
dev_dbg ( pfrt_log_dev - > parent_dev , " Error Status:%d \n " , data_info - > status ) ;
dev_dbg ( pfrt_log_dev - > parent_dev , " Error Extend Status:%d \n " ,
data_info - > ext_status ) ;
goto free_acpi_buffer ;
}
data_info - > max_data_size =
out_obj - > package . elements [ LOG_MAX_SZ_IDX ] . integer . value ;
data_info - > chunk1_addr_lo =
out_obj - > package . elements [ LOG_CHUNK1_LO_IDX ] . integer . value ;
data_info - > chunk1_addr_hi =
out_obj - > package . elements [ LOG_CHUNK1_HI_IDX ] . integer . value ;
data_info - > chunk1_size =
out_obj - > package . elements [ LOG_CHUNK1_SZ_IDX ] . integer . value ;
data_info - > chunk2_addr_lo =
out_obj - > package . elements [ LOG_CHUNK2_LO_IDX ] . integer . value ;
data_info - > chunk2_addr_hi =
out_obj - > package . elements [ LOG_CHUNK2_HI_IDX ] . integer . value ;
data_info - > chunk2_size =
out_obj - > package . elements [ LOG_CHUNK2_SZ_IDX ] . integer . value ;
data_info - > rollover_cnt =
out_obj - > package . elements [ LOG_ROLLOVER_CNT_IDX ] . integer . value ;
data_info - > reset_cnt =
out_obj - > package . elements [ LOG_RESET_CNT_IDX ] . integer . value ;
ret = 0 ;
free_acpi_buffer :
2022-11-18 09:32:18 +03:00
ACPI_FREE ( out_obj ) ;
2021-12-22 07:32:02 +03:00
return ret ;
}
static int set_pfrt_log_level ( int level , struct pfrt_log_device * pfrt_log_dev )
{
acpi_handle handle = ACPI_HANDLE ( pfrt_log_dev - > parent_dev ) ;
union acpi_object * out_obj , * obj , in_obj , in_buf ;
enum pfru_dsm_status status , ext_status ;
int ret = 0 ;
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 , & pfrt_log_guid ,
pfrt_log_dev - > info . log_revid , PFRT_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 ! = DSM_SUCCEED ) {
obj = & out_obj - > package . elements [ 1 ] ;
ext_status = obj - > integer . value ;
dev_dbg ( pfrt_log_dev - > parent_dev , " Error Status:%d \n " , status ) ;
dev_dbg ( pfrt_log_dev - > parent_dev , " Error Extend Status:%d \n " , ext_status ) ;
ret = - EBUSY ;
}
2022-11-18 09:32:18 +03:00
ACPI_FREE ( out_obj ) ;
2021-12-22 07:32:02 +03:00
return ret ;
}
static int get_pfrt_log_level ( struct pfrt_log_device * pfrt_log_dev )
{
acpi_handle handle = ACPI_HANDLE ( pfrt_log_dev - > parent_dev ) ;
union acpi_object * out_obj , * obj ;
enum pfru_dsm_status status , ext_status ;
int ret = - EBUSY ;
out_obj = acpi_evaluate_dsm_typed ( handle , & pfrt_log_guid ,
pfrt_log_dev - > info . log_revid , PFRT_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 ! = DSM_SUCCEED ) {
obj = & out_obj - > package . elements [ 1 ] ;
ext_status = obj - > integer . value ;
dev_dbg ( pfrt_log_dev - > parent_dev , " Error Status:%d \n " , status ) ;
dev_dbg ( pfrt_log_dev - > parent_dev , " Error Extend Status:%d \n " , ext_status ) ;
goto free_acpi_buffer ;
}
obj = & out_obj - > package . elements [ 2 ] ;
if ( obj - > type ! = ACPI_TYPE_INTEGER )
goto free_acpi_buffer ;
ret = obj - > integer . value ;
free_acpi_buffer :
2022-11-18 09:32:18 +03:00
ACPI_FREE ( out_obj ) ;
2021-12-22 07:32:02 +03:00
return ret ;
}
static int valid_log_level ( u32 level )
{
return level = = PFRT_LOG_ERR | | level = = PFRT_LOG_WARN | |
level = = PFRT_LOG_INFO | | level = = PFRT_LOG_VERB ;
}
static int valid_log_type ( u32 type )
{
return type = = PFRT_LOG_EXEC_IDX | | type = = PFRT_LOG_HISTORY_IDX ;
}
static inline int valid_log_revid ( u32 id )
{
return id = = PFRT_REVID_1 | | id = = PFRT_REVID_2 ;
}
static long pfrt_log_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
{
struct pfrt_log_device * pfrt_log_dev = to_pfrt_log_dev ( file ) ;
struct pfrt_log_data_info data_info ;
struct pfrt_log_info info ;
void __user * p ;
int ret = 0 ;
p = ( void __user * ) arg ;
switch ( cmd ) {
case PFRT_LOG_IOC_SET_INFO :
if ( copy_from_user ( & info , p , sizeof ( info ) ) )
return - EFAULT ;
if ( valid_log_revid ( info . log_revid ) )
pfrt_log_dev - > info . log_revid = info . log_revid ;
if ( valid_log_level ( info . log_level ) ) {
ret = set_pfrt_log_level ( info . log_level , pfrt_log_dev ) ;
if ( ret < 0 )
return ret ;
pfrt_log_dev - > info . log_level = info . log_level ;
}
if ( valid_log_type ( info . log_type ) )
pfrt_log_dev - > info . log_type = info . log_type ;
return 0 ;
case PFRT_LOG_IOC_GET_INFO :
info . log_level = get_pfrt_log_level ( pfrt_log_dev ) ;
if ( ret < 0 )
return ret ;
info . log_type = pfrt_log_dev - > info . log_type ;
info . log_revid = pfrt_log_dev - > info . log_revid ;
if ( copy_to_user ( p , & info , sizeof ( info ) ) )
return - EFAULT ;
return 0 ;
case PFRT_LOG_IOC_GET_DATA_INFO :
ret = get_pfrt_log_data_info ( & data_info , pfrt_log_dev ) ;
if ( ret )
return ret ;
if ( copy_to_user ( p , & data_info , sizeof ( struct pfrt_log_data_info ) ) )
return - EFAULT ;
return 0 ;
default :
return - ENOTTY ;
}
}
static int
pfrt_log_mmap ( struct file * file , struct vm_area_struct * vma )
{
struct pfrt_log_device * pfrt_log_dev ;
struct pfrt_log_data_info info ;
unsigned long psize , vsize ;
phys_addr_t base_addr ;
int ret ;
if ( vma - > vm_flags & VM_WRITE )
return - EROFS ;
/* changing from read to write with mprotect is not allowed */
2023-01-26 22:37:49 +03:00
vm_flags_clear ( vma , VM_MAYWRITE ) ;
2021-12-22 07:32:02 +03:00
pfrt_log_dev = to_pfrt_log_dev ( file ) ;
ret = get_pfrt_log_data_info ( & info , pfrt_log_dev ) ;
if ( ret )
return ret ;
base_addr = ( phys_addr_t ) ( ( info . chunk2_addr_hi < < 32 ) | info . chunk2_addr_lo ) ;
/* pfrt update has not been launched yet */
if ( ! base_addr )
return - ENODEV ;
psize = info . max_data_size ;
/* base address and total buffer size must be page aligned */
if ( ! PAGE_ALIGNED ( base_addr ) | | ! PAGE_ALIGNED ( psize ) )
return - ENODEV ;
vsize = vma - > vm_end - vma - > vm_start ;
if ( vsize > psize )
return - EINVAL ;
vma - > vm_page_prot = pgprot_noncached ( vma - > vm_page_prot ) ;
if ( io_remap_pfn_range ( vma , vma - > vm_start , PFN_DOWN ( base_addr ) ,
vsize , vma - > vm_page_prot ) )
return - EAGAIN ;
return 0 ;
}
static const struct file_operations acpi_pfrt_log_fops = {
. owner = THIS_MODULE ,
. mmap = pfrt_log_mmap ,
. unlocked_ioctl = pfrt_log_ioctl ,
. llseek = noop_llseek ,
} ;
2024-02-22 21:52:38 +03:00
static void acpi_pfrt_log_remove ( struct platform_device * pdev )
2021-12-22 07:32:02 +03:00
{
struct pfrt_log_device * pfrt_log_dev = platform_get_drvdata ( pdev ) ;
misc_deregister ( & pfrt_log_dev - > miscdev ) ;
}
static void pfrt_log_put_idx ( void * data )
{
struct pfrt_log_device * pfrt_log_dev = data ;
ida_free ( & pfrt_log_ida , pfrt_log_dev - > index ) ;
}
static int acpi_pfrt_log_probe ( struct platform_device * pdev )
{
acpi_handle handle = ACPI_HANDLE ( & pdev - > dev ) ;
struct pfrt_log_device * pfrt_log_dev ;
int ret ;
if ( ! acpi_has_method ( handle , " _DSM " ) ) {
dev_dbg ( & pdev - > dev , " Missing _DSM \n " ) ;
return - ENODEV ;
}
pfrt_log_dev = devm_kzalloc ( & pdev - > dev , sizeof ( * pfrt_log_dev ) , GFP_KERNEL ) ;
if ( ! pfrt_log_dev )
return - ENOMEM ;
ret = ida_alloc ( & pfrt_log_ida , GFP_KERNEL ) ;
if ( ret < 0 )
return ret ;
pfrt_log_dev - > index = ret ;
ret = devm_add_action_or_reset ( & pdev - > dev , pfrt_log_put_idx , pfrt_log_dev ) ;
if ( ret )
return ret ;
pfrt_log_dev - > info . log_revid = PFRT_DEFAULT_REV_ID ;
pfrt_log_dev - > parent_dev = & pdev - > dev ;
pfrt_log_dev - > miscdev . minor = MISC_DYNAMIC_MINOR ;
pfrt_log_dev - > miscdev . name = devm_kasprintf ( & pdev - > dev , GFP_KERNEL ,
" pfrt%d " ,
pfrt_log_dev - > index ) ;
if ( ! pfrt_log_dev - > miscdev . name )
return - ENOMEM ;
pfrt_log_dev - > miscdev . nodename = devm_kasprintf ( & pdev - > dev , GFP_KERNEL ,
" acpi_pfr_telemetry%d " ,
pfrt_log_dev - > index ) ;
if ( ! pfrt_log_dev - > miscdev . nodename )
return - ENOMEM ;
pfrt_log_dev - > miscdev . fops = & acpi_pfrt_log_fops ;
pfrt_log_dev - > miscdev . parent = & pdev - > dev ;
ret = misc_register ( & pfrt_log_dev - > miscdev ) ;
if ( ret )
return ret ;
platform_set_drvdata ( pdev , pfrt_log_dev ) ;
return 0 ;
}
static const struct acpi_device_id acpi_pfrt_log_ids [ ] = {
{ " INTC1081 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , acpi_pfrt_log_ids ) ;
static struct platform_driver acpi_pfrt_log_driver = {
. driver = {
. name = " pfr_telemetry " ,
. acpi_match_table = acpi_pfrt_log_ids ,
} ,
. probe = acpi_pfrt_log_probe ,
2024-02-22 21:52:38 +03:00
. remove_new = acpi_pfrt_log_remove ,
2021-12-22 07:32:02 +03:00
} ;
module_platform_driver ( acpi_pfrt_log_driver ) ;
MODULE_DESCRIPTION ( " Platform Firmware Runtime Update Telemetry driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;