2014-09-03 11:14:23 +04:00
/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship
*
* Copyright ( c ) 2014 Intel Corp
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation .
*
*/
/*
* Two functionalities included :
* 1. Export _TRT , _ART , via misc device interface to the userspace .
* 2. Provide parsing result to kernel drivers
*
*/
# include <linux/init.h>
# include <linux/export.h>
# include <linux/module.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# include <linux/acpi.h>
# include <linux/uaccess.h>
# include <linux/miscdevice.h>
# include "acpi_thermal_rel.h"
static acpi_handle acpi_thermal_rel_handle ;
static DEFINE_SPINLOCK ( acpi_thermal_rel_chrdev_lock ) ;
static int acpi_thermal_rel_chrdev_count ; /* #times opened */
static int acpi_thermal_rel_chrdev_exclu ; /* already open exclusive? */
static int acpi_thermal_rel_open ( struct inode * inode , struct file * file )
{
spin_lock ( & acpi_thermal_rel_chrdev_lock ) ;
if ( acpi_thermal_rel_chrdev_exclu | |
( acpi_thermal_rel_chrdev_count & & ( file - > f_flags & O_EXCL ) ) ) {
spin_unlock ( & acpi_thermal_rel_chrdev_lock ) ;
return - EBUSY ;
}
if ( file - > f_flags & O_EXCL )
acpi_thermal_rel_chrdev_exclu = 1 ;
acpi_thermal_rel_chrdev_count + + ;
spin_unlock ( & acpi_thermal_rel_chrdev_lock ) ;
return nonseekable_open ( inode , file ) ;
}
static int acpi_thermal_rel_release ( struct inode * inode , struct file * file )
{
spin_lock ( & acpi_thermal_rel_chrdev_lock ) ;
acpi_thermal_rel_chrdev_count - - ;
acpi_thermal_rel_chrdev_exclu = 0 ;
spin_unlock ( & acpi_thermal_rel_chrdev_lock ) ;
return 0 ;
}
/**
* acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling
*
* @ handle : ACPI handle of the device contains _TRT
* @ art_count : the number of valid entries resulted from parsing _TRT
* @ artp : pointer to pointer of array of art entries in parsing result
* @ create_dev : whether to create platform devices for target and source
*
*/
int acpi_parse_trt ( acpi_handle handle , int * trt_count , struct trt * * trtp ,
bool create_dev )
{
acpi_status status ;
int result = 0 ;
int i ;
int nr_bad_entries = 0 ;
struct trt * trts ;
struct acpi_device * adev ;
union acpi_object * p ;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
struct acpi_buffer element = { 0 , NULL } ;
struct acpi_buffer trt_format = { sizeof ( " RRNNNNNN " ) , " RRNNNNNN " } ;
if ( ! acpi_has_method ( handle , " _TRT " ) )
2014-12-09 23:34:03 +03:00
return - ENODEV ;
2014-09-03 11:14:23 +04:00
status = acpi_evaluate_object ( handle , " _TRT " , NULL , & buffer ) ;
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
p = buffer . pointer ;
if ( ! p | | ( p - > type ! = ACPI_TYPE_PACKAGE ) ) {
pr_err ( " Invalid _TRT data \n " ) ;
result = - EFAULT ;
goto end ;
}
* trt_count = p - > package . count ;
trts = kzalloc ( * trt_count * sizeof ( struct trt ) , GFP_KERNEL ) ;
if ( ! trts ) {
result = - ENOMEM ;
goto end ;
}
for ( i = 0 ; i < * trt_count ; i + + ) {
struct trt * trt = & trts [ i - nr_bad_entries ] ;
element . length = sizeof ( struct trt ) ;
element . pointer = trt ;
status = acpi_extract_package ( & ( p - > package . elements [ i ] ) ,
& trt_format , & element ) ;
if ( ACPI_FAILURE ( status ) ) {
nr_bad_entries + + ;
pr_warn ( " _TRT package %d is invalid, ignored \n " , i ) ;
continue ;
}
if ( ! create_dev )
continue ;
result = acpi_bus_get_device ( trt - > source , & adev ) ;
2015-01-04 09:24:23 +03:00
if ( result )
2014-09-03 11:14:23 +04:00
pr_warn ( " Failed to get source ACPI device \n " ) ;
result = acpi_bus_get_device ( trt - > target , & adev ) ;
2015-01-04 09:24:23 +03:00
if ( result )
2014-09-03 11:14:23 +04:00
pr_warn ( " Failed to get target ACPI device \n " ) ;
}
2014-12-09 05:01:31 +03:00
result = 0 ;
2014-09-03 11:14:23 +04:00
* trtp = trts ;
/* don't count bad entries */
* trt_count - = nr_bad_entries ;
end :
kfree ( buffer . pointer ) ;
return result ;
}
EXPORT_SYMBOL ( acpi_parse_trt ) ;
/**
* acpi_parse_art - Parse Active Relationship Table _ART
*
* @ handle : ACPI handle of the device contains _ART
* @ art_count : the number of valid entries resulted from parsing _ART
* @ artp : pointer to pointer of array of art entries in parsing result
* @ create_dev : whether to create platform devices for target and source
*
*/
int acpi_parse_art ( acpi_handle handle , int * art_count , struct art * * artp ,
bool create_dev )
{
acpi_status status ;
int result = 0 ;
int i ;
int nr_bad_entries = 0 ;
struct art * arts ;
struct acpi_device * adev ;
union acpi_object * p ;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER , NULL } ;
struct acpi_buffer element = { 0 , NULL } ;
struct acpi_buffer art_format = {
sizeof ( " RRNNNNNNNNNNN " ) , " RRNNNNNNNNNNN " } ;
if ( ! acpi_has_method ( handle , " _ART " ) )
2014-12-09 23:34:03 +03:00
return - ENODEV ;
2014-09-03 11:14:23 +04:00
status = acpi_evaluate_object ( handle , " _ART " , NULL , & buffer ) ;
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
p = buffer . pointer ;
if ( ! p | | ( p - > type ! = ACPI_TYPE_PACKAGE ) ) {
pr_err ( " Invalid _ART data \n " ) ;
result = - EFAULT ;
goto end ;
}
/* ignore p->package.elements[0], as this is _ART Revision field */
* art_count = p - > package . count - 1 ;
arts = kzalloc ( * art_count * sizeof ( struct art ) , GFP_KERNEL ) ;
if ( ! arts ) {
result = - ENOMEM ;
goto end ;
}
for ( i = 0 ; i < * art_count ; i + + ) {
struct art * art = & arts [ i - nr_bad_entries ] ;
element . length = sizeof ( struct art ) ;
element . pointer = art ;
status = acpi_extract_package ( & ( p - > package . elements [ i + 1 ] ) ,
& art_format , & element ) ;
if ( ACPI_FAILURE ( status ) ) {
pr_warn ( " _ART package %d is invalid, ignored " , i ) ;
nr_bad_entries + + ;
continue ;
}
if ( ! create_dev )
continue ;
if ( art - > source ) {
result = acpi_bus_get_device ( art - > source , & adev ) ;
2015-01-04 09:24:23 +03:00
if ( result )
2014-09-03 11:14:23 +04:00
pr_warn ( " Failed to get source ACPI device \n " ) ;
}
if ( art - > target ) {
result = acpi_bus_get_device ( art - > target , & adev ) ;
2015-01-04 09:24:23 +03:00
if ( result )
2014-09-03 11:14:23 +04:00
pr_warn ( " Failed to get source ACPI device \n " ) ;
}
}
* artp = arts ;
/* don't count bad entries */
* art_count - = nr_bad_entries ;
end :
kfree ( buffer . pointer ) ;
return result ;
}
EXPORT_SYMBOL ( acpi_parse_art ) ;
/* get device name from acpi handle */
static void get_single_name ( acpi_handle handle , char * name )
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER } ;
if ( ACPI_FAILURE ( acpi_get_name ( handle , ACPI_SINGLE_NAME , & buffer ) ) )
pr_warn ( " Failed get name from handle \n " ) ;
else {
memcpy ( name , buffer . pointer , ACPI_NAME_SIZE ) ;
kfree ( buffer . pointer ) ;
}
}
static int fill_art ( char __user * ubuf )
{
int i ;
int ret ;
int count ;
int art_len ;
struct art * arts = NULL ;
union art_object * art_user ;
ret = acpi_parse_art ( acpi_thermal_rel_handle , & count , & arts , false ) ;
if ( ret )
goto free_art ;
art_len = count * sizeof ( union art_object ) ;
art_user = kzalloc ( art_len , GFP_KERNEL ) ;
if ( ! art_user ) {
ret = - ENOMEM ;
goto free_art ;
}
/* now fill in user art data */
for ( i = 0 ; i < count ; i + + ) {
/* userspace art needs device name instead of acpi reference */
get_single_name ( arts [ i ] . source , art_user [ i ] . source_device ) ;
get_single_name ( arts [ i ] . target , art_user [ i ] . target_device ) ;
/* copy the rest int data in addition to source and target */
memcpy ( & art_user [ i ] . weight , & arts [ i ] . weight ,
sizeof ( u64 ) * ( ACPI_NR_ART_ELEMENTS - 2 ) ) ;
}
if ( copy_to_user ( ubuf , art_user , art_len ) )
ret = - EFAULT ;
kfree ( art_user ) ;
free_art :
kfree ( arts ) ;
return ret ;
}
static int fill_trt ( char __user * ubuf )
{
int i ;
int ret ;
int count ;
int trt_len ;
struct trt * trts = NULL ;
union trt_object * trt_user ;
ret = acpi_parse_trt ( acpi_thermal_rel_handle , & count , & trts , false ) ;
if ( ret )
goto free_trt ;
trt_len = count * sizeof ( union trt_object ) ;
trt_user = kzalloc ( trt_len , GFP_KERNEL ) ;
if ( ! trt_user ) {
ret = - ENOMEM ;
goto free_trt ;
}
/* now fill in user trt data */
for ( i = 0 ; i < count ; i + + ) {
/* userspace trt needs device name instead of acpi reference */
get_single_name ( trts [ i ] . source , trt_user [ i ] . source_device ) ;
get_single_name ( trts [ i ] . target , trt_user [ i ] . target_device ) ;
trt_user [ i ] . sample_period = trts [ i ] . sample_period ;
trt_user [ i ] . influence = trts [ i ] . influence ;
}
if ( copy_to_user ( ubuf , trt_user , trt_len ) )
ret = - EFAULT ;
kfree ( trt_user ) ;
free_trt :
kfree ( trts ) ;
return ret ;
}
static long acpi_thermal_rel_ioctl ( struct file * f , unsigned int cmd ,
unsigned long __arg )
{
int ret = 0 ;
unsigned long length = 0 ;
2014-11-05 02:53:34 +03:00
int count = 0 ;
2014-09-03 11:14:23 +04:00
char __user * arg = ( void __user * ) __arg ;
2014-12-09 23:34:03 +03:00
struct trt * trts = NULL ;
struct art * arts = NULL ;
2014-09-03 11:14:23 +04:00
switch ( cmd ) {
case ACPI_THERMAL_GET_TRT_COUNT :
2014-11-05 02:53:34 +03:00
ret = acpi_parse_trt ( acpi_thermal_rel_handle , & count ,
2014-09-03 11:14:23 +04:00
& trts , false ) ;
kfree ( trts ) ;
if ( ! ret )
return put_user ( count , ( unsigned long __user * ) __arg ) ;
return ret ;
case ACPI_THERMAL_GET_TRT_LEN :
2014-11-05 02:53:34 +03:00
ret = acpi_parse_trt ( acpi_thermal_rel_handle , & count ,
2014-09-03 11:14:23 +04:00
& trts , false ) ;
kfree ( trts ) ;
length = count * sizeof ( union trt_object ) ;
if ( ! ret )
return put_user ( length , ( unsigned long __user * ) __arg ) ;
return ret ;
case ACPI_THERMAL_GET_TRT :
return fill_trt ( arg ) ;
case ACPI_THERMAL_GET_ART_COUNT :
2014-11-05 02:53:34 +03:00
ret = acpi_parse_art ( acpi_thermal_rel_handle , & count ,
2014-09-03 11:14:23 +04:00
& arts , false ) ;
kfree ( arts ) ;
if ( ! ret )
return put_user ( count , ( unsigned long __user * ) __arg ) ;
return ret ;
case ACPI_THERMAL_GET_ART_LEN :
2014-11-05 02:53:34 +03:00
ret = acpi_parse_art ( acpi_thermal_rel_handle , & count ,
2014-09-03 11:14:23 +04:00
& arts , false ) ;
kfree ( arts ) ;
length = count * sizeof ( union art_object ) ;
if ( ! ret )
return put_user ( length , ( unsigned long __user * ) __arg ) ;
return ret ;
case ACPI_THERMAL_GET_ART :
return fill_art ( arg ) ;
default :
return - ENOTTY ;
}
}
static const struct file_operations acpi_thermal_rel_fops = {
. owner = THIS_MODULE ,
. open = acpi_thermal_rel_open ,
. release = acpi_thermal_rel_release ,
. unlocked_ioctl = acpi_thermal_rel_ioctl ,
. llseek = no_llseek ,
} ;
static struct miscdevice acpi_thermal_rel_misc_device = {
. minor = MISC_DYNAMIC_MINOR ,
" acpi_thermal_rel " ,
& acpi_thermal_rel_fops
} ;
int acpi_thermal_rel_misc_device_add ( acpi_handle handle )
{
acpi_thermal_rel_handle = handle ;
return misc_register ( & acpi_thermal_rel_misc_device ) ;
}
EXPORT_SYMBOL ( acpi_thermal_rel_misc_device_add ) ;
int acpi_thermal_rel_misc_device_remove ( acpi_handle handle )
{
misc_deregister ( & acpi_thermal_rel_misc_device ) ;
return 0 ;
}
EXPORT_SYMBOL ( acpi_thermal_rel_misc_device_remove ) ;
MODULE_AUTHOR ( " Zhang Rui <rui.zhang@intel.com> " ) ;
MODULE_AUTHOR ( " Jacob Pan <jacob.jun.pan@intel.com " ) ;
MODULE_DESCRIPTION ( " Intel acpi thermal rel misc dev driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;