2018-11-09 00:05:21 +03:00
// SPDX-License-Identifier: GPL-2.0
# include <linux/device.h>
# include <linux/err.h>
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/fsi-sbefifo.h>
# include <linux/gfp.h>
# include <linux/idr.h>
# include <linux/kernel.h>
# include <linux/list.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/fsi-occ.h>
# include <linux/of.h>
2020-11-20 04:03:14 +03:00
# include <linux/of_device.h>
2018-11-09 00:05:21 +03:00
# include <linux/platform_device.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
# include <asm/unaligned.h>
# define OCC_SRAM_BYTES 4096
# define OCC_CMD_DATA_BYTES 4090
# define OCC_RESP_DATA_BYTES 4089
2020-11-20 04:03:14 +03:00
# define OCC_P9_SRAM_CMD_ADDR 0xFFFBE000
# define OCC_P9_SRAM_RSP_ADDR 0xFFFBF000
# define OCC_P10_SRAM_CMD_ADDR 0xFFFFD000
# define OCC_P10_SRAM_RSP_ADDR 0xFFFFE000
# define OCC_P10_SRAM_MODE 0x58 /* Normal mode, OCB channel 2 */
2018-11-09 00:05:21 +03:00
/*
* Assume we don ' t have much FFDC , if we do we ' ll overflow and
* fail the command . This needs to be big enough for simple
* commands as well .
*/
# define OCC_SBE_STATUS_WORDS 32
# define OCC_TIMEOUT_MS 1000
# define OCC_CMD_IN_PRG_WAIT_MS 50
2020-11-20 04:03:14 +03:00
enum versions { occ_p9 , occ_p10 } ;
2018-11-09 00:05:21 +03:00
struct occ {
struct device * dev ;
struct device * sbefifo ;
char name [ 32 ] ;
int idx ;
2020-11-20 04:03:14 +03:00
enum versions version ;
2018-11-09 00:05:21 +03:00
struct miscdevice mdev ;
struct mutex occ_lock ;
} ;
# define to_occ(x) container_of((x), struct occ, mdev)
struct occ_response {
u8 seq_no ;
u8 cmd_type ;
u8 return_status ;
__be16 data_length ;
u8 data [ OCC_RESP_DATA_BYTES + 2 ] ; /* two bytes checksum */
} __packed ;
struct occ_client {
struct occ * occ ;
struct mutex lock ;
size_t data_size ;
size_t read_offset ;
u8 * buffer ;
} ;
# define to_client(x) container_of((x), struct occ_client, xfr)
static DEFINE_IDA ( occ_ida ) ;
static int occ_open ( struct inode * inode , struct file * file )
{
struct occ_client * client = kzalloc ( sizeof ( * client ) , GFP_KERNEL ) ;
struct miscdevice * mdev = file - > private_data ;
struct occ * occ = to_occ ( mdev ) ;
if ( ! client )
return - ENOMEM ;
client - > buffer = ( u8 * ) __get_free_page ( GFP_KERNEL ) ;
if ( ! client - > buffer ) {
kfree ( client ) ;
return - ENOMEM ;
}
client - > occ = occ ;
mutex_init ( & client - > lock ) ;
file - > private_data = client ;
/* We allocate a 1-page buffer, make sure it all fits */
BUILD_BUG_ON ( ( OCC_CMD_DATA_BYTES + 3 ) > PAGE_SIZE ) ;
BUILD_BUG_ON ( ( OCC_RESP_DATA_BYTES + 7 ) > PAGE_SIZE ) ;
return 0 ;
}
static ssize_t occ_read ( struct file * file , char __user * buf , size_t len ,
loff_t * offset )
{
struct occ_client * client = file - > private_data ;
ssize_t rc = 0 ;
if ( ! client )
return - ENODEV ;
if ( len > OCC_SRAM_BYTES )
return - EINVAL ;
mutex_lock ( & client - > lock ) ;
/* This should not be possible ... */
if ( WARN_ON_ONCE ( client - > read_offset > client - > data_size ) ) {
rc = - EIO ;
goto done ;
}
/* Grab how much data we have to read */
rc = min ( len , client - > data_size - client - > read_offset ) ;
if ( copy_to_user ( buf , client - > buffer + client - > read_offset , rc ) )
rc = - EFAULT ;
else
client - > read_offset + = rc ;
done :
mutex_unlock ( & client - > lock ) ;
return rc ;
}
static ssize_t occ_write ( struct file * file , const char __user * buf ,
size_t len , loff_t * offset )
{
struct occ_client * client = file - > private_data ;
size_t rlen , data_length ;
u16 checksum = 0 ;
ssize_t rc , i ;
u8 * cmd ;
if ( ! client )
return - ENODEV ;
if ( len > ( OCC_CMD_DATA_BYTES + 3 ) | | len < 3 )
return - EINVAL ;
mutex_lock ( & client - > lock ) ;
/* Construct the command */
cmd = client - > buffer ;
/* Sequence number (we could increment and compare with response) */
cmd [ 0 ] = 1 ;
/*
* Copy the user command ( assume user data follows the occ command
* format )
* byte 0 : command type
* bytes 1 - 2 : data length ( msb first )
* bytes 3 - n : data
*/
if ( copy_from_user ( & cmd [ 1 ] , buf , len ) ) {
rc = - EFAULT ;
goto done ;
}
/* Extract data length */
data_length = ( cmd [ 2 ] < < 8 ) + cmd [ 3 ] ;
if ( data_length > OCC_CMD_DATA_BYTES ) {
rc = - EINVAL ;
goto done ;
}
/* Calculate checksum */
for ( i = 0 ; i < data_length + 4 ; + + i )
checksum + = cmd [ i ] ;
cmd [ data_length + 4 ] = checksum > > 8 ;
cmd [ data_length + 5 ] = checksum & 0xFF ;
/* Submit command */
rlen = PAGE_SIZE ;
rc = fsi_occ_submit ( client - > occ - > dev , cmd , data_length + 6 , cmd ,
& rlen ) ;
if ( rc )
goto done ;
/* Set read tracking data */
client - > data_size = rlen ;
client - > read_offset = 0 ;
/* Done */
rc = len ;
done :
mutex_unlock ( & client - > lock ) ;
return rc ;
}
static int occ_release ( struct inode * inode , struct file * file )
{
struct occ_client * client = file - > private_data ;
free_page ( ( unsigned long ) client - > buffer ) ;
kfree ( client ) ;
return 0 ;
}
static const struct file_operations occ_fops = {
. owner = THIS_MODULE ,
. open = occ_open ,
. read = occ_read ,
. write = occ_write ,
. release = occ_release ,
} ;
static int occ_verify_checksum ( struct occ_response * resp , u16 data_length )
{
/* Fetch the two bytes after the data for the checksum. */
u16 checksum_resp = get_unaligned_be16 ( & resp - > data [ data_length ] ) ;
u16 checksum ;
u16 i ;
checksum = resp - > seq_no ;
checksum + = resp - > cmd_type ;
checksum + = resp - > return_status ;
checksum + = ( data_length > > 8 ) + ( data_length & 0xFF ) ;
for ( i = 0 ; i < data_length ; + + i )
checksum + = resp - > data [ i ] ;
if ( checksum ! = checksum_resp )
return - EBADMSG ;
return 0 ;
}
2020-11-20 04:03:14 +03:00
static int occ_getsram ( struct occ * occ , u32 offset , void * data , ssize_t len )
2018-11-09 00:05:21 +03:00
{
u32 data_len = ( ( len + 7 ) / 8 ) * 8 ; /* must be multiples of 8 B */
2020-11-20 04:03:14 +03:00
size_t cmd_len , resp_len , resp_data_len ;
__be32 * resp , cmd [ 6 ] ;
int idx = 0 , rc ;
2018-11-09 00:05:21 +03:00
/*
* Magic sequence to do SBE getsram command . SBE will fetch data from
* specified SRAM address .
*/
2020-11-20 04:03:14 +03:00
switch ( occ - > version ) {
default :
case occ_p9 :
cmd_len = 5 ;
cmd [ 2 ] = cpu_to_be32 ( 1 ) ; /* Normal mode */
cmd [ 3 ] = cpu_to_be32 ( OCC_P9_SRAM_RSP_ADDR + offset ) ;
break ;
case occ_p10 :
idx = 1 ;
cmd_len = 6 ;
cmd [ 2 ] = cpu_to_be32 ( OCC_P10_SRAM_MODE ) ;
cmd [ 3 ] = 0 ;
cmd [ 4 ] = cpu_to_be32 ( OCC_P10_SRAM_RSP_ADDR + offset ) ;
break ;
}
cmd [ 0 ] = cpu_to_be32 ( cmd_len ) ;
2018-11-09 00:05:21 +03:00
cmd [ 1 ] = cpu_to_be32 ( SBEFIFO_CMD_GET_OCC_SRAM ) ;
2020-11-20 04:03:14 +03:00
cmd [ 4 + idx ] = cpu_to_be32 ( data_len ) ;
2018-11-09 00:05:21 +03:00
resp_len = ( data_len > > 2 ) + OCC_SBE_STATUS_WORDS ;
resp = kzalloc ( resp_len < < 2 , GFP_KERNEL ) ;
if ( ! resp )
return - ENOMEM ;
2020-11-20 04:03:14 +03:00
rc = sbefifo_submit ( occ - > sbefifo , cmd , cmd_len , resp , & resp_len ) ;
2018-11-09 00:05:21 +03:00
if ( rc )
goto free ;
rc = sbefifo_parse_status ( occ - > sbefifo , SBEFIFO_CMD_GET_OCC_SRAM ,
resp , resp_len , & resp_len ) ;
if ( rc )
goto free ;
resp_data_len = be32_to_cpu ( resp [ resp_len - 1 ] ) ;
if ( resp_data_len ! = data_len ) {
dev_err ( occ - > dev , " SRAM read expected %d bytes got %zd \n " ,
data_len , resp_data_len ) ;
rc = - EBADMSG ;
} else {
memcpy ( data , resp , len ) ;
}
free :
/* Convert positive SBEI status */
if ( rc > 0 ) {
dev_err ( occ - > dev , " SRAM read returned failure status: %08x \n " ,
rc ) ;
rc = - EBADMSG ;
}
kfree ( resp ) ;
return rc ;
}
2020-11-20 04:03:14 +03:00
static int occ_putsram ( struct occ * occ , const void * data , ssize_t len )
2018-11-09 00:05:21 +03:00
{
size_t cmd_len , buf_len , resp_len , resp_data_len ;
u32 data_len = ( ( len + 7 ) / 8 ) * 8 ; /* must be multiples of 8 B */
__be32 * buf ;
2020-11-20 04:03:14 +03:00
int idx = 0 , rc ;
cmd_len = ( occ - > version = = occ_p10 ) ? 6 : 5 ;
2018-11-09 00:05:21 +03:00
/*
* We use the same buffer for command and response , make
* sure it ' s big enough
*/
resp_len = OCC_SBE_STATUS_WORDS ;
2020-11-20 04:03:14 +03:00
cmd_len + = data_len > > 2 ;
2018-11-09 00:05:21 +03:00
buf_len = max ( cmd_len , resp_len ) ;
buf = kzalloc ( buf_len < < 2 , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
/*
* Magic sequence to do SBE putsram command . SBE will transfer
* data to specified SRAM address .
*/
buf [ 0 ] = cpu_to_be32 ( cmd_len ) ;
buf [ 1 ] = cpu_to_be32 ( SBEFIFO_CMD_PUT_OCC_SRAM ) ;
2020-11-20 04:03:14 +03:00
switch ( occ - > version ) {
default :
case occ_p9 :
buf [ 2 ] = cpu_to_be32 ( 1 ) ; /* Normal mode */
buf [ 3 ] = cpu_to_be32 ( OCC_P9_SRAM_CMD_ADDR ) ;
break ;
case occ_p10 :
idx = 1 ;
buf [ 2 ] = cpu_to_be32 ( OCC_P10_SRAM_MODE ) ;
buf [ 3 ] = 0 ;
buf [ 4 ] = cpu_to_be32 ( OCC_P10_SRAM_CMD_ADDR ) ;
break ;
}
buf [ 4 + idx ] = cpu_to_be32 ( data_len ) ;
memcpy ( & buf [ 5 + idx ] , data , len ) ;
2018-11-09 00:05:21 +03:00
rc = sbefifo_submit ( occ - > sbefifo , buf , cmd_len , buf , & resp_len ) ;
if ( rc )
goto free ;
rc = sbefifo_parse_status ( occ - > sbefifo , SBEFIFO_CMD_PUT_OCC_SRAM ,
buf , resp_len , & resp_len ) ;
if ( rc )
goto free ;
if ( resp_len ! = 1 ) {
dev_err ( occ - > dev , " SRAM write response length invalid: %zd \n " ,
resp_len ) ;
rc = - EBADMSG ;
} else {
resp_data_len = be32_to_cpu ( buf [ 0 ] ) ;
if ( resp_data_len ! = data_len ) {
dev_err ( occ - > dev ,
" SRAM write expected %d bytes got %zd \n " ,
data_len , resp_data_len ) ;
rc = - EBADMSG ;
}
}
free :
/* Convert positive SBEI status */
if ( rc > 0 ) {
dev_err ( occ - > dev , " SRAM write returned failure status: %08x \n " ,
rc ) ;
rc = - EBADMSG ;
}
kfree ( buf ) ;
return rc ;
}
static int occ_trigger_attn ( struct occ * occ )
{
__be32 buf [ OCC_SBE_STATUS_WORDS ] ;
2020-11-20 04:03:14 +03:00
size_t cmd_len , resp_len , resp_data_len ;
int idx = 0 , rc ;
2018-11-09 00:05:21 +03:00
2020-11-20 04:03:14 +03:00
BUILD_BUG_ON ( OCC_SBE_STATUS_WORDS < 8 ) ;
2018-11-09 00:05:21 +03:00
resp_len = OCC_SBE_STATUS_WORDS ;
2020-11-20 04:03:14 +03:00
switch ( occ - > version ) {
default :
case occ_p9 :
cmd_len = 7 ;
buf [ 2 ] = cpu_to_be32 ( 3 ) ; /* Circular mode */
buf [ 3 ] = 0 ;
break ;
case occ_p10 :
idx = 1 ;
cmd_len = 8 ;
buf [ 2 ] = cpu_to_be32 ( 0xd0 ) ; /* Circular mode, OCB Channel 1 */
buf [ 3 ] = 0 ;
buf [ 4 ] = 0 ;
break ;
}
buf [ 0 ] = cpu_to_be32 ( cmd_len ) ; /* Chip-op length in words */
2018-11-09 00:05:21 +03:00
buf [ 1 ] = cpu_to_be32 ( SBEFIFO_CMD_PUT_OCC_SRAM ) ;
2020-11-20 04:03:14 +03:00
buf [ 4 + idx ] = cpu_to_be32 ( 8 ) ; /* Data length in bytes */
buf [ 5 + idx ] = cpu_to_be32 ( 0x20010000 ) ; /* Trigger OCC attention */
buf [ 6 + idx ] = 0 ;
2018-11-09 00:05:21 +03:00
2020-11-20 04:03:14 +03:00
rc = sbefifo_submit ( occ - > sbefifo , buf , cmd_len , buf , & resp_len ) ;
2018-11-09 00:05:21 +03:00
if ( rc )
goto error ;
rc = sbefifo_parse_status ( occ - > sbefifo , SBEFIFO_CMD_PUT_OCC_SRAM ,
buf , resp_len , & resp_len ) ;
if ( rc )
goto error ;
if ( resp_len ! = 1 ) {
dev_err ( occ - > dev , " SRAM attn response length invalid: %zd \n " ,
resp_len ) ;
rc = - EBADMSG ;
} else {
resp_data_len = be32_to_cpu ( buf [ 0 ] ) ;
if ( resp_data_len ! = 8 ) {
dev_err ( occ - > dev ,
" SRAM attn expected 8 bytes got %zd \n " ,
resp_data_len ) ;
rc = - EBADMSG ;
}
}
error :
/* Convert positive SBEI status */
if ( rc > 0 ) {
dev_err ( occ - > dev , " SRAM attn returned failure status: %08x \n " ,
rc ) ;
rc = - EBADMSG ;
}
return rc ;
}
int fsi_occ_submit ( struct device * dev , const void * request , size_t req_len ,
void * response , size_t * resp_len )
{
const unsigned long timeout = msecs_to_jiffies ( OCC_TIMEOUT_MS ) ;
const unsigned long wait_time =
msecs_to_jiffies ( OCC_CMD_IN_PRG_WAIT_MS ) ;
struct occ * occ = dev_get_drvdata ( dev ) ;
struct occ_response * resp = response ;
2019-07-02 18:47:42 +03:00
u8 seq_no ;
2018-11-09 00:05:21 +03:00
u16 resp_data_length ;
unsigned long start ;
int rc ;
if ( ! occ )
return - ENODEV ;
if ( * resp_len < 7 ) {
dev_dbg ( dev , " Bad resplen %zd \n " , * resp_len ) ;
return - EINVAL ;
}
mutex_lock ( & occ - > occ_lock ) ;
2019-07-02 18:47:42 +03:00
/* Extract the seq_no from the command (first byte) */
seq_no = * ( const u8 * ) request ;
2020-11-20 04:03:14 +03:00
rc = occ_putsram ( occ , request , req_len ) ;
2018-11-09 00:05:21 +03:00
if ( rc )
goto done ;
rc = occ_trigger_attn ( occ ) ;
if ( rc )
goto done ;
/* Read occ response header */
start = jiffies ;
do {
2020-11-20 04:03:14 +03:00
rc = occ_getsram ( occ , 0 , resp , 8 ) ;
2018-11-09 00:05:21 +03:00
if ( rc )
goto done ;
2019-07-02 18:47:42 +03:00
if ( resp - > return_status = = OCC_RESP_CMD_IN_PRG | |
resp - > seq_no ! = seq_no ) {
2018-11-09 00:05:21 +03:00
rc = - ETIMEDOUT ;
2019-07-02 18:47:42 +03:00
if ( time_after ( jiffies , start + timeout ) ) {
dev_err ( occ - > dev , " resp timeout status=%02x "
" resp seq_no=%d our seq_no=%d \n " ,
resp - > return_status , resp - > seq_no ,
seq_no ) ;
goto done ;
}
2018-11-09 00:05:21 +03:00
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
schedule_timeout ( wait_time ) ;
}
} while ( rc ) ;
/* Extract size of response data */
resp_data_length = get_unaligned_be16 ( & resp - > data_length ) ;
/* Message size is data length + 5 bytes header + 2 bytes checksum */
if ( ( resp_data_length + 7 ) > * resp_len ) {
rc = - EMSGSIZE ;
goto done ;
}
dev_dbg ( dev , " resp_status=%02x resp_data_len=%d \n " ,
resp - > return_status , resp_data_length ) ;
/* Grab the rest */
if ( resp_data_length > 1 ) {
/* already got 3 bytes resp, also need 2 bytes checksum */
2020-11-20 04:03:14 +03:00
rc = occ_getsram ( occ , 8 , & resp - > data [ 3 ] , resp_data_length - 1 ) ;
2018-11-09 00:05:21 +03:00
if ( rc )
goto done ;
}
* resp_len = resp_data_length + 7 ;
rc = occ_verify_checksum ( resp , resp_data_length ) ;
done :
mutex_unlock ( & occ - > occ_lock ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( fsi_occ_submit ) ;
static int occ_unregister_child ( struct device * dev , void * data )
{
struct platform_device * hwmon_dev = to_platform_device ( dev ) ;
platform_device_unregister ( hwmon_dev ) ;
return 0 ;
}
static int occ_probe ( struct platform_device * pdev )
{
int rc ;
u32 reg ;
struct occ * occ ;
struct platform_device * hwmon_dev ;
struct device * dev = & pdev - > dev ;
struct platform_device_info hwmon_dev_info = {
. parent = dev ,
. name = " occ-hwmon " ,
} ;
occ = devm_kzalloc ( dev , sizeof ( * occ ) , GFP_KERNEL ) ;
if ( ! occ )
return - ENOMEM ;
2020-11-20 04:03:14 +03:00
occ - > version = ( uintptr_t ) of_device_get_match_data ( dev ) ;
2018-11-09 00:05:21 +03:00
occ - > dev = dev ;
occ - > sbefifo = dev - > parent ;
mutex_init ( & occ - > occ_lock ) ;
if ( dev - > of_node ) {
rc = of_property_read_u32 ( dev - > of_node , " reg " , & reg ) ;
if ( ! rc ) {
/* make sure we don't have a duplicate from dts */
occ - > idx = ida_simple_get ( & occ_ida , reg , reg + 1 ,
GFP_KERNEL ) ;
if ( occ - > idx < 0 )
occ - > idx = ida_simple_get ( & occ_ida , 1 , INT_MAX ,
GFP_KERNEL ) ;
} else {
occ - > idx = ida_simple_get ( & occ_ida , 1 , INT_MAX ,
GFP_KERNEL ) ;
}
} else {
occ - > idx = ida_simple_get ( & occ_ida , 1 , INT_MAX , GFP_KERNEL ) ;
}
platform_set_drvdata ( pdev , occ ) ;
snprintf ( occ - > name , sizeof ( occ - > name ) , " occ%d " , occ - > idx ) ;
occ - > mdev . fops = & occ_fops ;
occ - > mdev . minor = MISC_DYNAMIC_MINOR ;
occ - > mdev . name = occ - > name ;
occ - > mdev . parent = dev ;
rc = misc_register ( & occ - > mdev ) ;
if ( rc ) {
dev_err ( dev , " failed to register miscdevice: %d \n " , rc ) ;
ida_simple_remove ( & occ_ida , occ - > idx ) ;
return rc ;
}
hwmon_dev_info . id = occ - > idx ;
hwmon_dev = platform_device_register_full ( & hwmon_dev_info ) ;
2020-07-13 06:33:13 +03:00
if ( IS_ERR ( hwmon_dev ) )
2018-11-09 00:05:21 +03:00
dev_warn ( dev , " failed to create hwmon device \n " ) ;
return 0 ;
}
static int occ_remove ( struct platform_device * pdev )
{
struct occ * occ = platform_get_drvdata ( pdev ) ;
misc_deregister ( & occ - > mdev ) ;
device_for_each_child ( & pdev - > dev , NULL , occ_unregister_child ) ;
ida_simple_remove ( & occ_ida , occ - > idx ) ;
return 0 ;
}
static const struct of_device_id occ_match [ ] = {
2020-11-20 04:03:14 +03:00
{
. compatible = " ibm,p9-occ " ,
. data = ( void * ) occ_p9
} ,
{
. compatible = " ibm,p10-occ " ,
. data = ( void * ) occ_p10
} ,
2018-11-09 00:05:21 +03:00
{ } ,
} ;
static struct platform_driver occ_driver = {
. driver = {
. name = " occ " ,
. of_match_table = occ_match ,
} ,
. probe = occ_probe ,
. remove = occ_remove ,
} ;
static int occ_init ( void )
{
return platform_driver_register ( & occ_driver ) ;
}
static void occ_exit ( void )
{
platform_driver_unregister ( & occ_driver ) ;
ida_destroy ( & occ_ida ) ;
}
module_init ( occ_init ) ;
module_exit ( occ_exit ) ;
MODULE_AUTHOR ( " Eddie James <eajames@linux.ibm.com> " ) ;
MODULE_DESCRIPTION ( " BMC P9 OCC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;