#CHIPSEC: Platform Security Assessment Framework
#Copyright (c) 2010-2021, Intel Corporation
#
#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; Version 2.
#
#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.
#
#Contact information:
#chipsec@intel.com
#



# -------------------------------------------------------------------------------
#
# CHIPSEC: Platform Hardware Security Assessment Framework
#
# -------------------------------------------------------------------------------

"""
Access to Embedded Controller (EC)

Usage:

    >>> write_command( command )
    >>> write_data( data )
    >>> read_data()
    >>> read_memory( offset )
    >>> write_memory( offset, data )
    >>> read_memory_extended( word_offset )
    >>> write_memory_extended( word_offset, data )
    >>> read_range( start_offset, size )
    >>> write_range( start_offset, buffer )

"""

from chipsec.hal import hal_base
from chipsec.logger import print_buffer

#
# Embedded Controller ACPI ports
#
IO_PORT_EC_DATA        = 0x62
IO_PORT_EC_COMMAND     = 0x66
IO_PORT_EC_STATUS      = 0x66

IO_PORT_EC_INDEX       = 0x380
IO_PORT_EC_INDEX_ADDRH = (IO_PORT_EC_INDEX + 0x1)
IO_PORT_EC_INDEX_ADDRL = (IO_PORT_EC_INDEX + 0x2)
IO_PORT_EC_INDEX_DATA  = (IO_PORT_EC_INDEX + 0x3)


EC_STS_OBF         = 0x01 # EC Output buffer full
EC_STS_IBF         = 0x02 # EC Input buffer empty


#
# Embedded Controller ACPI commands
# These commands should be submitted to EC ACPI I/O ports
#
EC_COMMAND_ACPI_READ        = 0x080  # Read EC ACPI memory
EC_COMMAND_ACPI_WRITE       = 0x081  # Write EC ACPI memory
EC_COMMAND_ACPI_LOCK        = 0x082  # Lock EC for burst use
EC_COMMAND_ACPI_UNLOCK      = 0x083  # Unlock EC from burst use
EC_COMMAND_ACPI_QUERY       = 0x084  # Query EC event
EC_COMMAND_ACPI_READ_EXT    = 0x0F0  # Read EC ACPI extended memory
EC_COMMAND_ACPI_WRITE_EXT   = 0x0F1  # Write EC ACPI extended memory

class EC(hal_base.HALBase):

    #
    # EC ACPI memory access
    #

    # Wait for EC input buffer empty
    def _wait_ec_inbuf_empty( self ):
        to = 1000
        while (self.cs.io.read_port_byte(IO_PORT_EC_STATUS) & EC_STS_IBF) and to: to = to - 1
        return True

    # Wait for EC output buffer full
    def _wait_ec_outbuf_full( self ):
        to = 1000
        while not ( self.cs.io.read_port_byte(IO_PORT_EC_STATUS) & EC_STS_OBF ) and to: to = to - 1
        return True

    def write_command( self, command ):
        self._wait_ec_inbuf_empty()
        return self.cs.io.write_port_byte( IO_PORT_EC_COMMAND, command )

    def write_data( self, data ):
        self._wait_ec_inbuf_empty()
        return self.cs.io.write_port_byte( IO_PORT_EC_DATA, data )

    def read_data( self ):
        if not self._wait_ec_outbuf_full(): return None
        return self.cs.io.read_port_byte( IO_PORT_EC_DATA )

    def read_memory( self, offset ):
        self.write_command( EC_COMMAND_ACPI_READ )
        self.write_data( offset )
        return self.read_data()

    def write_memory( self, offset, data ):
        self.write_command( EC_COMMAND_ACPI_WRITE )
        self.write_data( offset )
        return self.write_data( data )

    def read_memory_extended( self, word_offset ):
        self.write_command( EC_COMMAND_ACPI_READ )
        self.write_data( 0x2 )
        self.write_data( word_offset & 0xFF )
        self.write_command( EC_COMMAND_ACPI_READ_EXT )
        self.write_data( word_offset >> 8 )
        return self.read_data()

    def write_memory_extended( self, word_offset, data ):
        self.write_command( EC_COMMAND_ACPI_WRITE )
        self.write_data( 0x2 )
        self.write_data( word_offset & 0xFF )
        self.write_command( EC_COMMAND_ACPI_WRITE_EXT )
        self.write_data( word_offset >> 8 )
        return self.write_data( data )

    def read_range( self, start_offset, size ):
        buffer = [chr(0xFF)] *size
        #self.write_command( EC_COMMAND_ACPI_READ )
        for i in range (size):
            #self.write_data( start_offset + i )
            #buffer[i] = chr( self.read_data() )
            if start_offset + i < 0x100:
                buffer[i] = chr( self.read_memory( start_offset + i ) )
            else:
                buffer[i] = chr( self.read_memory_extended( start_offset + i ) )

        if self.logger.HAL:
            self.logger.log( "[ec] read EC memory from offset {:X} size {:X}:".format(start_offset, size) )
            print_buffer( buffer )
        return buffer

    def write_range( self, start_offset, buffer ):
        size = len(buffer)
        for i in range(size):
            self.write_memory( start_offset + i, ord(buffer[i]) )
        if self.logger.HAL:
            self.logger.log( "[ec] write EC memory to offset {:X} size {:X}:".format(start_offset, size) )
            print_buffer( buffer )
        return True

    #
    # EC Intex I/O access
    #
    def read_idx( self, offset ):
        self.cs.io.write_port_byte( IO_PORT_EC_INDEX_ADDRL, offset & 0xFF )
        self.cs.io.write_port_byte( IO_PORT_EC_INDEX_ADDRH, (offset>>8) & 0xFF )
        value = self.cs.io.read_port_byte( IO_PORT_EC_INDEX_DATA )
        if self.logger.HAL: self.logger.log( "[ec] index read: offset 0x{:02X} > 0x{:02X}:".format(offset, value) )
        return value

    def write_idx( self, offset, value ):
        if self.logger.HAL: self.logger.log( "[ec] index write: offset 0x{:02X} < 0x{:02X}:".format(offset, value) )
        self.cs.io.write_port_byte( IO_PORT_EC_INDEX_ADDRL, offset & 0xFF )
        self.cs.io.write_port_byte( IO_PORT_EC_INDEX_ADDRH, (offset>>8) & 0xFF )
        self.cs.io.write_port_byte( IO_PORT_EC_INDEX_DATA, value & 0xFF )
        return True
