blob: 526bf855582e5896eb960d9f5b355980b2f6359a [file] [log] [blame]
/*
* Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <errno.h>
#include <byteswap.h>
#include <ipxe/uaccess.h>
#include <ipxe/acpi.h>
#include <ipxe/interface.h>
/** @file
*
* ACPI support functions
*
*/
/** Colour for debug messages */
#define colour FADT_SIGNATURE
/** ACPI table finder
*
* May be overridden at link time to inject tables for testing.
*/
typeof ( acpi_find ) *acpi_finder __attribute__ (( weak )) = acpi_find;
/******************************************************************************
*
* Utility functions
*
******************************************************************************
*/
/**
* Compute ACPI table checksum
*
* @v table Any ACPI table
* @ret checksum 0 if checksum is good
*/
static uint8_t acpi_checksum ( userptr_t table ) {
struct acpi_header acpi;
uint8_t sum = 0;
uint8_t data = 0;
unsigned int i;
/* Read table length */
copy_from_user ( &acpi.length, table,
offsetof ( typeof ( acpi ), length ),
sizeof ( acpi.length ) );
/* Compute checksum */
for ( i = 0 ; i < le32_to_cpu ( acpi.length ) ; i++ ) {
copy_from_user ( &data, table, i, sizeof ( data ) );
sum += data;
}
return sum;
}
/**
* Fix up ACPI table checksum
*
* @v acpi ACPI table header
*/
void acpi_fix_checksum ( struct acpi_header *acpi ) {
/* Update checksum */
acpi->checksum -= acpi_checksum ( virt_to_user ( acpi ) );
}
/**
* Locate ACPI table
*
* @v signature Requested table signature
* @v index Requested index of table with this signature
* @ret table Table, or UNULL if not found
*/
userptr_t acpi_table ( uint32_t signature, unsigned int index ) {
return ( *acpi_finder ) ( signature, index );
}
/**
* Locate ACPI table via RSDT
*
* @v signature Requested table signature
* @v index Requested index of table with this signature
* @ret table Table, or UNULL if not found
*/
userptr_t acpi_find_via_rsdt ( uint32_t signature, unsigned int index ) {
struct acpi_header acpi;
struct acpi_rsdt *rsdtab;
typeof ( rsdtab->entry[0] ) entry;
userptr_t rsdt;
userptr_t table;
size_t len;
unsigned int count;
unsigned int i;
/* Locate RSDT */
rsdt = acpi_find_rsdt();
if ( ! rsdt ) {
DBG ( "RSDT not found\n" );
return UNULL;
}
/* Read RSDT header */
copy_from_user ( &acpi, rsdt, 0, sizeof ( acpi ) );
if ( acpi.signature != cpu_to_le32 ( RSDT_SIGNATURE ) ) {
DBGC ( colour, "RSDT %#08lx has invalid signature:\n",
user_to_phys ( rsdt, 0 ) );
DBGC_HDA ( colour, user_to_phys ( rsdt, 0 ), &acpi,
sizeof ( acpi ) );
return UNULL;
}
len = le32_to_cpu ( acpi.length );
if ( len < sizeof ( rsdtab->acpi ) ) {
DBGC ( colour, "RSDT %#08lx has invalid length:\n",
user_to_phys ( rsdt, 0 ) );
DBGC_HDA ( colour, user_to_phys ( rsdt, 0 ), &acpi,
sizeof ( acpi ) );
return UNULL;
}
/* Calculate number of entries */
count = ( ( len - sizeof ( rsdtab->acpi ) ) / sizeof ( entry ) );
/* Search through entries */
for ( i = 0 ; i < count ; i++ ) {
/* Get table address */
copy_from_user ( &entry, rsdt,
offsetof ( typeof ( *rsdtab ), entry[i] ),
sizeof ( entry ) );
/* Read table header */
table = phys_to_user ( entry );
copy_from_user ( &acpi.signature, table, 0,
sizeof ( acpi.signature ) );
/* Check table signature */
if ( acpi.signature != cpu_to_le32 ( signature ) )
continue;
/* Check index */
if ( index-- )
continue;
/* Check table integrity */
if ( acpi_checksum ( table ) != 0 ) {
DBGC ( colour, "RSDT %#08lx found %s with bad "
"checksum at %08lx\n", user_to_phys ( rsdt, 0 ),
acpi_name ( signature ),
user_to_phys ( table, 0 ) );
break;
}
DBGC ( colour, "RSDT %#08lx found %s at %08lx\n",
user_to_phys ( rsdt, 0 ), acpi_name ( signature ),
user_to_phys ( table, 0 ) );
return table;
}
DBGC ( colour, "RSDT %#08lx could not find %s\n",
user_to_phys ( rsdt, 0 ), acpi_name ( signature ) );
return UNULL;
}
/**
* Extract value from DSDT/SSDT
*
* @v zsdt DSDT or SSDT
* @v signature Signature (e.g. "_S5_")
* @v data Data buffer
* @v extract Extraction method
* @ret rc Return status code
*/
static int acpi_zsdt ( userptr_t zsdt, uint32_t signature, void *data,
int ( * extract ) ( userptr_t zsdt, size_t len,
size_t offset, void *data ) ) {
struct acpi_header acpi;
uint32_t buf;
size_t offset;
size_t len;
int rc;
/* Read table header */
copy_from_user ( &acpi, zsdt, 0, sizeof ( acpi ) );
len = le32_to_cpu ( acpi.length );
/* Locate signature */
for ( offset = sizeof ( acpi ) ;
( ( offset + sizeof ( buf ) /* signature */ ) < len ) ;
offset++ ) {
/* Check signature */
copy_from_user ( &buf, zsdt, offset, sizeof ( buf ) );
if ( buf != cpu_to_le32 ( signature ) )
continue;
DBGC ( zsdt, "DSDT/SSDT %#08lx found %s at offset %#zx\n",
user_to_phys ( zsdt, 0 ), acpi_name ( signature ),
offset );
/* Attempt to extract data */
if ( ( rc = extract ( zsdt, len, offset, data ) ) == 0 )
return 0;
}
return -ENOENT;
}
/**
* Extract value from DSDT/SSDT
*
* @v signature Signature (e.g. "_S5_")
* @v data Data buffer
* @v extract Extraction method
* @ret rc Return status code
*/
int acpi_extract ( uint32_t signature, void *data,
int ( * extract ) ( userptr_t zsdt, size_t len,
size_t offset, void *data ) ) {
struct acpi_fadt fadtab;
userptr_t fadt;
userptr_t dsdt;
userptr_t ssdt;
unsigned int i;
int rc;
/* Try DSDT first */
fadt = acpi_table ( FADT_SIGNATURE, 0 );
if ( fadt ) {
copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) );
dsdt = phys_to_user ( fadtab.dsdt );
if ( ( rc = acpi_zsdt ( dsdt, signature, data,
extract ) ) == 0 )
return 0;
}
/* Try all SSDTs */
for ( i = 0 ; ; i++ ) {
ssdt = acpi_table ( SSDT_SIGNATURE, i );
if ( ! ssdt )
break;
if ( ( rc = acpi_zsdt ( ssdt, signature, data,
extract ) ) == 0 )
return 0;
}
DBGC ( colour, "ACPI could not find \"%s\"\n",
acpi_name ( signature ) );
return -ENOENT;
}
/******************************************************************************
*
* Descriptors
*
******************************************************************************
*/
/**
* Add ACPI descriptor
*
* @v desc ACPI descriptor
*/
void acpi_add ( struct acpi_descriptor *desc ) {
/* Add to list of descriptors */
ref_get ( desc->refcnt );
list_add_tail ( &desc->list, &desc->model->descs );
}
/**
* Remove ACPI descriptor
*
* @v desc ACPI descriptor
*/
void acpi_del ( struct acpi_descriptor *desc ) {
/* Remove from list of descriptors */
list_check_contains_entry ( desc, &desc->model->descs, list );
list_del ( &desc->list );
ref_put ( desc->refcnt );
}
/**
* Get object's ACPI descriptor
*
* @v intf Interface
* @ret desc ACPI descriptor, or NULL
*/
struct acpi_descriptor * acpi_describe ( struct interface *intf ) {
struct interface *dest;
acpi_describe_TYPE ( void * ) *op =
intf_get_dest_op ( intf, acpi_describe, &dest );
void *object = intf_object ( dest );
struct acpi_descriptor *desc;
if ( op ) {
desc = op ( object );
} else {
desc = NULL;
}
intf_put ( dest );
return desc;
}
/**
* Install ACPI tables
*
* @v install Table installation method
* @ret rc Return status code
*/
int acpi_install ( int ( * install ) ( struct acpi_header *acpi ) ){
struct acpi_model *model;
int rc;
for_each_table_entry ( model, ACPI_MODELS ) {
if ( ( rc = model->install ( install ) ) != 0 )
return rc;
}
return 0;
}