2022-02-08 18:36:32 +03:00
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2018-2021 Intel Corporation
2022-02-08 18:36:34 +03:00
# include <linux/bitfield.h>
2022-02-08 18:36:32 +03:00
# include <linux/peci.h>
2022-02-08 18:36:35 +03:00
# include <linux/peci-cpu.h>
2022-02-08 18:36:32 +03:00
# include <linux/slab.h>
# include "internal.h"
/*
* PECI device can be removed using sysfs , but the removal can also happen as
* a result of controller being removed .
* Mutex is used to protect PECI device from being double - deleted .
*/
static DEFINE_MUTEX ( peci_device_del_lock ) ;
2022-02-08 18:36:34 +03:00
# define REVISION_NUM_MASK GENMASK(15, 8)
static int peci_get_revision ( struct peci_device * device , u8 * revision )
{
struct peci_request * req ;
u64 dib ;
req = peci_xfer_get_dib ( device ) ;
if ( IS_ERR ( req ) )
return PTR_ERR ( req ) ;
/*
* PECI device may be in a state where it is unable to return a proper
* DIB , in which case it returns 0 as DIB value .
* Let ' s treat this as an error to avoid carrying on with the detection
* using invalid revision .
*/
dib = peci_request_dib_read ( req ) ;
if ( dib = = 0 ) {
peci_request_free ( req ) ;
return - EIO ;
}
* revision = FIELD_GET ( REVISION_NUM_MASK , dib ) ;
peci_request_free ( req ) ;
return 0 ;
}
static int peci_get_cpu_id ( struct peci_device * device , u32 * cpu_id )
{
struct peci_request * req ;
int ret ;
req = peci_xfer_pkg_cfg_readl ( device , PECI_PCS_PKG_ID , PECI_PKG_ID_CPU_ID ) ;
if ( IS_ERR ( req ) )
return PTR_ERR ( req ) ;
ret = peci_request_status ( req ) ;
if ( ret )
goto out_req_free ;
* cpu_id = peci_request_data_readl ( req ) ;
out_req_free :
peci_request_free ( req ) ;
return ret ;
}
static unsigned int peci_x86_cpu_family ( unsigned int sig )
{
unsigned int x86 ;
x86 = ( sig > > 8 ) & 0xf ;
if ( x86 = = 0xf )
x86 + = ( sig > > 20 ) & 0xff ;
return x86 ;
}
static unsigned int peci_x86_cpu_model ( unsigned int sig )
{
unsigned int fam , model ;
fam = peci_x86_cpu_family ( sig ) ;
model = ( sig > > 4 ) & 0xf ;
if ( fam > = 0x6 )
model + = ( ( sig > > 16 ) & 0xf ) < < 4 ;
return model ;
}
static int peci_device_info_init ( struct peci_device * device )
{
u8 revision ;
u32 cpu_id ;
int ret ;
ret = peci_get_cpu_id ( device , & cpu_id ) ;
if ( ret )
return ret ;
device - > info . family = peci_x86_cpu_family ( cpu_id ) ;
device - > info . model = peci_x86_cpu_model ( cpu_id ) ;
ret = peci_get_revision ( device , & revision ) ;
if ( ret )
return ret ;
device - > info . peci_revision = revision ;
device - > info . socket_id = device - > addr - PECI_BASE_ADDR ;
return 0 ;
}
2022-02-08 18:36:32 +03:00
static int peci_detect ( struct peci_controller * controller , u8 addr )
{
/*
* PECI Ping is a command encoded by tx_len = 0 , rx_len = 0.
* We expect correct Write FCS if the device at the target address
* is able to respond .
*/
struct peci_request req = { 0 } ;
int ret ;
mutex_lock ( & controller - > bus_lock ) ;
ret = controller - > ops - > xfer ( controller , addr , & req ) ;
mutex_unlock ( & controller - > bus_lock ) ;
return ret ;
}
static bool peci_addr_valid ( u8 addr )
{
return addr > = PECI_BASE_ADDR & & addr < PECI_BASE_ADDR + PECI_DEVICE_NUM_MAX ;
}
static int peci_dev_exists ( struct device * dev , void * data )
{
struct peci_device * device = to_peci_device ( dev ) ;
u8 * addr = data ;
if ( device - > addr = = * addr )
return - EBUSY ;
return 0 ;
}
int peci_device_create ( struct peci_controller * controller , u8 addr )
{
struct peci_device * device ;
int ret ;
if ( ! peci_addr_valid ( addr ) )
return - EINVAL ;
/* Check if we have already detected this device before. */
ret = device_for_each_child ( & controller - > dev , & addr , peci_dev_exists ) ;
if ( ret )
return 0 ;
ret = peci_detect ( controller , addr ) ;
if ( ret ) {
/*
* Device not present or host state doesn ' t allow successful
* detection at this time .
*/
if ( ret = = - EIO | | ret = = - ETIMEDOUT )
return 0 ;
return ret ;
}
device = kzalloc ( sizeof ( * device ) , GFP_KERNEL ) ;
if ( ! device )
return - ENOMEM ;
device_initialize ( & device - > dev ) ;
device - > addr = addr ;
device - > dev . parent = & controller - > dev ;
device - > dev . bus = & peci_bus_type ;
device - > dev . type = & peci_device_type ;
2022-02-08 18:36:34 +03:00
ret = peci_device_info_init ( device ) ;
if ( ret )
goto err_put ;
2022-02-08 18:36:32 +03:00
ret = dev_set_name ( & device - > dev , " %d-%02x " , controller - > id , device - > addr ) ;
if ( ret )
goto err_put ;
ret = device_add ( & device - > dev ) ;
if ( ret )
goto err_put ;
return 0 ;
err_put :
put_device ( & device - > dev ) ;
return ret ;
}
void peci_device_destroy ( struct peci_device * device )
{
mutex_lock ( & peci_device_del_lock ) ;
if ( ! device - > deleted ) {
device_unregister ( & device - > dev ) ;
device - > deleted = true ;
}
mutex_unlock ( & peci_device_del_lock ) ;
}
2022-02-08 18:36:34 +03:00
int __peci_driver_register ( struct peci_driver * driver , struct module * owner ,
const char * mod_name )
{
driver - > driver . bus = & peci_bus_type ;
driver - > driver . owner = owner ;
driver - > driver . mod_name = mod_name ;
if ( ! driver - > probe ) {
pr_err ( " peci: trying to register driver without probe callback \n " ) ;
return - EINVAL ;
}
if ( ! driver - > id_table ) {
pr_err ( " peci: trying to register driver without device id table \n " ) ;
return - EINVAL ;
}
return driver_register ( & driver - > driver ) ;
}
EXPORT_SYMBOL_NS_GPL ( __peci_driver_register , PECI ) ;
void peci_driver_unregister ( struct peci_driver * driver )
{
driver_unregister ( & driver - > driver ) ;
}
EXPORT_SYMBOL_NS_GPL ( peci_driver_unregister , PECI ) ;
2022-02-08 18:36:32 +03:00
static void peci_device_release ( struct device * dev )
{
struct peci_device * device = to_peci_device ( dev ) ;
kfree ( device ) ;
}
struct device_type peci_device_type = {
2022-02-08 18:36:33 +03:00
. groups = peci_device_groups ,
2022-02-08 18:36:32 +03:00
. release = peci_device_release ,
} ;