blob: 8731311fd538d9905cbe2f37715dfe1b8f34bc7f [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""The python wrapper of the hammerd API."""
from __future__ import print_function
import ctypes
import sys
# Load hammerd-api library.
_DLL = ctypes.CDLL('libhammerd-api.so')
ENTROPY_SIZE = ctypes.c_int.in_dll(_DLL, 'kEntropySize').value
SHA256_DIGEST_LENGTH = ctypes.c_int.in_dll(_DLL, 'kSha256DigestLength').value
X25519_PUBLIC_VALUE_LEN = ctypes.c_int.in_dll(
_DLL, 'kX25519PublicValueLen').value
class Enum(object):
"""Enumeration wrapper. ToStr converts enumerator to string."""
@classmethod
def ToStr(cls, val):
for name, enum_value in cls.__dict__.items():
if name.startswith('__'):
continue
if val == enum_value:
return name
return 'unknown(%d)' % val
class UpdateExtraCommand(Enum):
"""The enumeration of extra vendor subcommands."""
ImmediateReset = 0
JumpToRW = 1
StayInRO = 2
UnlockRW = 3
UnlockRollback = 4
InjectEntropy = 5
PairChallenge = 6
TouchpadInfo = 7
class SectionName(Enum):
"""The enumeration of the image sections."""
RO = 0
RW = 1
Invalid = 2
class ChallengeStatus(Enum):
"""The returned value of the PairChallenge."""
ChallengePassed = 0
ChallengeFailed = 1
NeedInjectEntropy = 2
UnknownError = 3
class FirstResponsePdu(ctypes.Structure):
"""Struct of response of first PDU from src/platform/ec/include/update_fw.h"""
_pack_ = 1
_fields_ = [('return_value', ctypes.c_uint32),
('header_type', ctypes.c_uint16),
('protocol_version', ctypes.c_uint16),
('maximum_pdu_size', ctypes.c_uint32),
('flash_protection', ctypes.c_uint32),
('offset', ctypes.c_uint32),
('version', ctypes.c_ubyte * 32),
('min_rollback', ctypes.c_int32),
('key_version', ctypes.c_uint32)]
def __str__(self):
ret = ''
for field_name, unused_field_type in self._fields_:
ret += '%s: %s\n' % (field_name, getattr(self, field_name))
return ret
class TouchpadInfo(ctypes.Structure):
"""touchpad_info struct from src/platform/ec/include/update_fw.h"""
_pack_ = 1
_fields_ = [('status', ctypes.c_ubyte),
('reserved', ctypes.c_ubyte),
('vendor', ctypes.c_ushort),
('fw_address', ctypes.c_uint),
('fw_size', ctypes.c_uint),
('allowed_fw_hash', ctypes.c_ubyte * SHA256_DIGEST_LENGTH),
('id', ctypes.c_ushort),
('fw_version', ctypes.c_ushort),
('fw_checksum', ctypes.c_ushort)]
class ByteString(ctypes.Structure):
"""Intermediary type between Python string to C++ string.
Because ctypes doesn't support C++ std::string, we need to pass C-style
char pointer and the size of string first. Then convert it to std::string in
other side.
"""
_fields_ = [
('ptr', ctypes.c_char_p),
('size', ctypes.c_size_t)]
class WrapperMetaclass(type):
"""The metaclass of the wrapper class.
Each wrapper class should declare the method signature in "METHODS" fields,
which is a list of (method name, [arg0 type, arg1 type, ...], return type).
Also, each class should initiate the instance to "self.object" field.
"""
def __new__(cls, name, bases, dct):
for method_name, argtypes, restype in dct['METHODS']:
dct[method_name] = WrapperMetaclass.GenerateMethod(
name, method_name, argtypes, restype)
return super(WrapperMetaclass, cls).__new__(cls, name, bases, dct)
@staticmethod
def GenerateMethod(cls_name, method_name, argtypes, restype):
"""Generates the wrapper function by the function signature."""
def method(self, *args):
print('Call %s' % method_name, file=sys.stderr)
if len(args) != len(argtypes) - 1: # argtypes includes object itself.
raise TypeError('%s expected %d arguments, got %d.' %
(method_name, len(argtypes) - 1, len(args)))
# Convert Python string to ByteString.
args = list(args) # Convert args from tuple to list.
for idx, arg_type in enumerate(argtypes[1:]):
if arg_type == ctypes.POINTER(ByteString):
args[idx] = WrapperMetaclass.ConvertString(args[idx])
elif arg_type == ctypes.c_char_p:
args[idx] = args[idx].encode('utf-8')
func = getattr(_DLL, '%s_%s' % (cls_name, method_name))
func.argtypes = argtypes
func.restype = restype
ret = func(self.object, *args)
if restype == ctypes.c_char_p:
ret = ret.decode('utf-8')
return ret
return method
@staticmethod
def ConvertString(string):
"""Converts Python string to a ctypes ByteString pointer.
Args:
string: a Python string.
Returns:
A ctypes pointer to ByteString.
"""
buffer_size = len(string)
buffer_ptr = ctypes.cast(ctypes.create_string_buffer(string, buffer_size),
ctypes.c_char_p)
return ctypes.byref(ByteString(buffer_ptr, buffer_size))
class FirmwareUpdater(object, metaclass=WrapperMetaclass):
"""The wrapper of FirmwareUpdater class."""
METHODS = [
('LoadEcImage',
[ctypes.c_voidp, ctypes.POINTER(ByteString)], ctypes.c_bool),
('LoadTouchpadImage',
[ctypes.c_voidp, ctypes.POINTER(ByteString)], ctypes.c_bool),
('TryConnectUsb', [ctypes.c_voidp], ctypes.c_bool),
('CloseUsb', [ctypes.c_voidp], None),
('SendFirstPdu', [ctypes.c_voidp], ctypes.c_bool),
('SendDone', [ctypes.c_voidp], None),
('InjectEntropy', [ctypes.c_voidp], ctypes.c_bool),
('InjectEntropyWithPayload',
[ctypes.c_voidp, ctypes.POINTER(ByteString)], ctypes.c_bool),
('SendSubcommand', [ctypes.c_voidp, ctypes.c_uint16], ctypes.c_bool),
('SendSubcommandWithPayload',
[ctypes.c_voidp, ctypes.c_uint16, ctypes.POINTER(ByteString)],
ctypes.c_bool),
('SendSubcommandReceiveResponse',
[ctypes.c_voidp, ctypes.c_uint16, ctypes.POINTER(ByteString),
ctypes.c_voidp, ctypes.c_size_t], ctypes.c_bool),
('TransferImage', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool),
('TransferTouchpadFirmware',
[ctypes.c_voidp, ctypes.c_uint32, ctypes.c_size_t], ctypes.c_bool),
('CurrentSection', [ctypes.c_voidp], ctypes.c_int),
('UpdatePossible', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool),
('VersionMismatch', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool),
('IsSectionLocked', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool),
('UnlockRW', [ctypes.c_voidp], ctypes.c_bool),
('IsRollbackLocked', [ctypes.c_voidp], ctypes.c_bool),
('UnlockRollback', [ctypes.c_voidp], ctypes.c_bool),
('GetFirstResponsePdu',
[ctypes.c_voidp], ctypes.POINTER(FirstResponsePdu)),
('GetSectionVersion', [ctypes.c_voidp, ctypes.c_int], ctypes.c_char_p),
]
def __init__(self, vendor_id, product_id, path=None):
func = _DLL.FirmwareUpdater_New
func.argtypes = [ctypes.c_uint16, ctypes.c_uint16, ctypes.c_char_p]
func.restype = ctypes.c_void_p
if path is not None:
path = path.encode('utf-8')
self.object = func(vendor_id, product_id, path)
class PairManager(object, metaclass=WrapperMetaclass):
"""The wrapper of FirmwareUpdater class."""
METHODS = [
('PairChallenge', [ctypes.c_voidp, ctypes.c_voidp,
ctypes.POINTER(ctypes.c_uint8)], ctypes.c_int),
]
def __init__(self):
func = _DLL.PairManager_New
func.argtypes = []
func.restype = ctypes.c_void_p
self.object = func()